你不再用使用 Redux、Mobx、Flux 等狀態管理了

Unstated Next README 的中文翻譯html

前言

這個庫的做者但願使用 React 內置 API ,直接實現狀態管理的功能。看完這個庫的說明後,沒有想到代碼能夠這個玩。短短几行代碼,僅僅使用 React Hooks ,就實現了狀態管理的功能。react

看完以後,第一想法就是翻譯成中文,分享給其餘人。提交 Pull Request 後,庫做者將個人翻譯合併了。同時做者歡迎將 README 翻譯成其餘語言,如下是所有翻譯內容,不妥之處歡迎指正或 Pull Request.git

Unstated Next

永遠沒必要再考慮 React 狀態管理了,僅僅 200 字節的狀態管理解決方案。github

  • React Hooks React Hooks 用作你全部的狀態管理。
  • ~200 bytes min+gz.
  • 熟悉的 API 僅僅使用了 React,沒有依賴第三方庫。
  • 最小 API 只需 5 分鐘學習。
  • TypeScript 編寫 推斷代碼更容易,易於編寫 React 代碼。

可是,最重要的問題是:這比 Redux 更好嗎? 答案多是。npm

  • 它更小。 比 Redux 小 40 倍。
  • 它更快。 組件性能問題。
  • 它更容易學習。 你必須已經知道 React Hooks 和 Context 。只需使用它們,它們就會嗨起來。
  • 更容易集成。 一次集成一個組件,而且輕鬆與其餘 React 庫集成。
  • 它更容易測試。 測試 reducers 純屬浪費你的時間,這個庫使你更容易測試 React 組件。
  • 它更容易進行類型檢查。 旨在使你的大多數類型可推斷。
  • 它是最小的。 僅僅使用了 React 。

你本身看着辦吧!dom

安裝

npm install --save unstated-next
複製代碼

Example

import React, { useState } from "react"
import { createContainer } from "unstated-next"
import { render } from "react-dom"

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div> <button onClick={counter.decrement}>-</button> <span>{counter.count}</span> <button onClick={counter.increment}>+</button> </div>
  )
}

function App() {
  return (
    <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } render(<App />, document.getElementById("root")) 複製代碼

API

createContainer(useHook)

import { createContainer } from "unstated-next"

function useCustomHook() {
  let [value, setInput] = useState()
  let onChange = e => setValue(e.currentTarget.value)
  return { value, onChange }
}

let Container = createContainer(useCustomHook)
// Container === { Provider, useContainer }
複製代碼

<Container.Provider>

function ParentComponent() {
  return (
    <Container.Provider> <ChildComponent /> </Container.Provider> ) } 複製代碼

Container.useContainer()

function ChildComponent() {
  let input = Container.useContainer()
  return <input value={input.value} onChange={input.onChange} /> } 複製代碼

useContainer(Container)

import { useContainer } from "unstated-next"

function ChildComponent() {
  let input = useContainer(Container)
  return <input value={input.value} onChange={input.onChange} /> } 複製代碼

指南

若是你之前從未使用過 React Hooks,我不建議你往下看,請先閱讀 React 官網的 React Hooks 文檔ide

首先,使用 React Hooks,你能夠建立這樣一個組件:函數

function CounterDisplay() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return (
    <div> <button onClick={decrement}>-</button> <p>You clicked {count} times</p> <button onClick={increment}>+</button> </div>
  )
}
複製代碼

而後,若是你想共享組件的邏輯,你能夠把它寫在組件外面,自定義一個 hook:工具

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}

function CounterDisplay() {
  let counter = useCounter()
  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div>
  )
}
複製代碼

可是,除了共享邏輯以外,你還想共享狀態,你會怎麼作呢?性能

這個時候,context 就發揮了做用:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContext(null)

function CounterDisplay() {
  let counter = useContext(Counter)
  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div>
  )
}

function App() {
  let counter = useCounter()
  return (
    <Counter.Provider value={counter}> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 複製代碼

這很棒,也很完美,更多人應該編寫這樣的代碼。

但有時咱們須要更多的結構和特定的 API 設計才能使其始終保持正確。

經過引入 createContainer() 函數,你能夠將自定義 hooks 做爲 containers,而且定義明確的 API,防止錯誤使用。

import { createContainer } from "unstated-next"

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div>
  )
}

function App() {
  return (
    <Counter.Provider> <CounterDisplay /> <CounterDisplay /> </Counter.Provider> ) } 複製代碼

下面是先後的代碼對比:

- import { createContext, useContext } from "react"
+ import { createContainer } from "unstated-next"

  function useCounter() {
    ...
  }

- let Counter = createContext(null)
+ let Counter = createContainer(useCounter)

  function CounterDisplay() {
- let counter = useContext(Counter)
+ let counter = Counter.useContainer()
    return (
      <div>
        ...
      </div>
    )
  }

  function App() {
- let counter = useCounter()
    return (
- <Counter.Provider value={counter}>
+ <Counter.Provider>
        <CounterDisplay />
        <CounterDisplay />
      </Counter.Provider>
    )
  }
複製代碼

若是你正在使用 TypeScript(我鼓勵你瞭解更多關於它的信息),這也有助於 TypeScript 的內置推斷作得更好。只要你的自定義 hook 類型是完善的,那麼類型都會自動推斷。

提示

提示 #1: 組合 Containers

由於咱們只使用了自定義 React hooks,因此能夠在其餘 hooks 內部組合 containers。

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment, setCount }
}

let Counter = createContainer(useCounter)

function useResettableCounter() {
  let counter = Counter.useContainer()
  let reset = () => counter.setCount(0)
  return { ...counter, reset }
}
複製代碼

提示 #2: 保持 Containers 很小

這對於保持 containers 小而集中很是有用。 若是你想在 containers 中對代碼進行邏輯拆分,那麼這一點很是重要。只需將它們移動到本身的 hooks 中,僅保存 containers 的狀態便可。

function useCount() {
  return useState(0)
}

let Count = createContainer(useCount)

function useCounter() {
  let [count, setCount] = Count.useContainer()
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  let reset = () => setCount(0)
  return { count, decrement, increment, reset }
}
複製代碼

提示 #3: 優化組件

unstated-next 無需優化。全部你要作的優化,都是標準的 React 優化。

1) 經過拆分組件來優化耗時的子樹

優化前:

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <div> <div> <div> <div>SUPER EXPENSIVE RENDERING STUFF</div> </div> </div> </div> </div>
  )
}
複製代碼

優化後:

function ExpensiveComponent() {
  return (
    <div> <div> <div> <div>SUPER EXPENSIVE RENDERING STUFF</div> </div> </div> </div>
  )
}

function CounterDisplay() {
  let counter = Counter.useContainer()
  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> <ExpensiveComponent /> </div>
  )
}
複製代碼

2) 使用 useMemo() 優化耗時的操做

優化前:

function CounterDisplay(props) {
  let counter = Counter.useContainer()

  // 每次 `counter` 改變都要從新計算這個值,很是耗時
  let expensiveValue = expensiveComputation(props.input)

  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div>
  )
}
複製代碼

優化後:

function CounterDisplay(props) {
  let counter = Counter.useContainer()

  // 僅在輸入更改時從新計算這個值
  let expensiveValue = useMemo(() => {
    return expensiveComputation(props.input)
  }, [props.input])

  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div>
  )
}
複製代碼

3) 使用 React.memo()、useCallback() 減小從新渲染次數

優化前:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = () => setCount(count - 1)
  let increment = () => setCount(count + 1)
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

function CounterDisplay(props) {
  let counter = Counter.useContainer()
  return (
    <div> <button onClick={counter.decrement}>-</button> <p>You clicked {counter.count} times</p> <button onClick={counter.increment}>+</button> </div>
  )
}
複製代碼

優化後:

function useCounter() {
  let [count, setCount] = useState(0)
  let decrement = useCallback(() => setCount(count - 1), [count])
  let increment = useCallback(() => setCount(count + 1), [count])
  return { count, decrement, increment }
}

let Counter = createContainer(useCounter)

let CounterDisplayInner = React.memo(props => {
  return (
    <div> <button onClick={props.decrement}>-</button> <p>You clicked {props.count} times</p> <button onClick={props.increment}>+</button> </div>
  )
})

function CounterDisplay(props) {
  let counter = Counter.useContainer()
  return <CounterDisplayInner {...counter} /> } 複製代碼

與 Unstated 的關係

我認爲這個庫是 Unstated 精神的繼承者。由於我相信 React 在狀態管理方面已經很是出色,惟一缺乏的就是輕鬆共享狀態和邏輯,因此我建立了 Unstated 。我建立的 Unstated 是 React 共享狀態和邏輯的 最小 解決方案。

然而,使用 Hooks,React 在共享狀態和邏輯方面能夠作得更好。我甚至認爲 Unstated 成爲了避免必要的抽象。

可是,我認爲不少開發人員都在努力瞭解如何使用 React Hooks 共享狀態和邏輯,從而實現應用程序共享狀態。這可能只是文檔和社區動力的問題,但我認爲一個新的 API 能夠彌補這種心理差距。

這個 API 就是 Unstated Next。 它不是 React 中共享狀態和邏輯的最小 API,而是用於理解如何在 React 中共享狀態和邏輯的最小 API

我一直給 React 站隊。我但願 React 能夠贏。 我但願社區放棄像 Redux 這樣的狀態管理庫,並找到使用 React 內置工具鏈的更好方法。

若是你不想使用 Unstated,你只想使用 React 自己,我很是鼓勵你這麼作。 寫關於它的博客文章! 討論它! 在社區中傳播你的知識。

unstated 遷移

我故意將其發佈爲單獨的包,由於它是對原有 API 的徹底重寫。 這樣,你能夠逐步安裝和遷移。

請向我提供有關該遷移過程的反饋,由於在接下來的幾個月裏,我但願獲得這些反饋並作如下兩件事:

  • 確保 unstated-next 知足 unstated 使用者的全部需求。
  • 確保 unstated 使用者的代碼能夠完整地遷移到 unstated-next

我能夠將 API 新增到二者的任意一個倉庫中,從而使開發人員工做得更輕鬆。 對於 unstated-next,我將保證新增的 API 儘量小,同時,我也會盡可能保持庫很小。

將來,我可能會將 unstated-next 合併爲 unstated 的主要版本。 unstated-next 仍然存在,這樣你就能夠安裝 unstated@2unstated-next。 當你完成遷移後,你能夠更新到 unstated@3 ,同時刪除 unstated-next(確保更新你全部的引入,這應該只是一個查找和替換的過程)。

儘管這是一個重大的 API 更改,我但願你儘量輕鬆地完成此遷移。我正在使用最新的 React Hooks API ,爲你進行優化,而不是使用原有的 Unstated.Container 代碼。請隨意提供有關如何作得更好的反饋。

首發 nusr.github.io/

相關文章
相關標籤/搜索