本文须要先行了解 Hooks 的基础知识。html
React 状态管理实现有两种,一种是 Flux 架构的,例如 Redux,其经过 Context 实现全局状态共享;另外一种是响应式的,例如 Mobx,经过可观测对象和 HOC 实现状态共享。react
在 Hooks 出来后,以前的经过 props 的状态解决方案就有些过于繁琐了,鉴于以前的状态管理的复杂,前两天我写了一个状态管理工具 Piex Store,彻底面向对象,基于 Hooks,不借助于 Context 实现状态共享。git
本文将基于其核心原理,逐步实现一个最简单的状态管理工具,其核心代码只有 13 行。github
Hooks API 出来后,Function Component(函数组件,如下简称 FC) 也有了本身的状态,并且能够自定义 Custom Hook,这便让咱们对组件状态有个更多的操做可能性。如下是一个简单的自定义 Hooks:redux
const useCounter = () => { const [count, setCount] = useState(0); const increment = useCallback(() => { setCount(count + 1); }, [count]); const decrement = useCallback(() => { setCount(count - 1); }, [count]); return {count, increment, decrement}; } 复制代码
若是要使用的话须要在一个 FC 里:数组
const Counter = () => { const {count, increment, decrement} = useCounter(); return ( <article> <p>{count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </article> ) } 复制代码
这样咱们就实现了一个简单的 custom hooks,能够复用对 count
的操做逻辑。markdown
可是这里有一个问题,你们能够运行这个 例子,咱们使用两个 Counter
组件的话,它们的 count
是互不关联的,二者之间没有任何关系。若是咱们有办法让 count
共享,每次修改状态就能在全部用到的地方同步到状态,不就是状态共享吗!架构
共享状态的一个问题就是如何把状态同步到全部组件并更新页面,其实经过 useState
就能够轻松实现,下面咱们看一个例子:frontend
const App = () => { const [,setState] = useState(Math.random()); setTimeout(()=>{ setState(Math.random()); }, 200); return ( <p>{Date.now()}</p> ) }; 复制代码
点击 这里 查看实际运行效果。dom
能够发现这里没有取 useState
的第一个参数,而只是用了第二个参数更新状态,实际上每 200ms 后页面上就显示不一样的时间戳。
其实 useState
并非什么黑魔法,具体实现原理能够看个人 这篇文章。简单来说,就是咱们每次 setState
时,若是参数和前一个 state
不相等,React 就会从新运行函数组件,把返回的值作 DOM Diff 来更新页面,因此关键就在于 setState
,若是咱们掌握了 setState,就掌握了更新组件的时机。
咱们经过上面的例子知道 useState
返回数组的第二个值,这里称为 setState
,能够控制组件的渲染。
那么若是有一个方法,把全部用到共享状态的组件都建立一个 useState
,并把第二个参数存储起来,每次更新共享状态时,把全部的 setState
都运行一遍,那么不就能够更新全部组件的状态了吗?不就实现状态共享了吗?
下面咱们经过一个简单的例子看一下:
let _count = 0; let _setters = []; const useCounter = () => { const [, setCount] = useState(_count); _setters.push(setCount); const increment = useCallback(() => { _count++; _setters.forEach(setState => setState(_count)); }, [_count]); const decrement = useCallback(() => { _count--; _setters.forEach(setState => setState(_count)); }, [_count]); return {count:_count, increment, decrement}; } const Counter = () => { const {count, increment, decrement} = useCounter(); return ( <article> <p>{count}</p> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </article> ) } const App = () => { return ( <div> <Counter /> <Counter /> </div> ) } 复制代码
运行效果点击 这里 查看。
这个例子是基于第一个 custom hook 的例子改动的,能够发现点击一个按钮,页面上两个 Counter
组件的 count
值都变了。
这是由于我把 useCounter
的状态存到全局的 _count
变量中了,而且把 useState
的第二个参数也都收集到全局的 _setters
数组中了,每次操做 increment
或者 decrement
时,就会先改变 _count
的值,而后触发 setters
中的 setState
,这样全部 Counter
组件都会更新啦。并且返回的是 _count
变量重命名为 count
,因此每一个组件的 setState
被触发后都会经过 _count
获得最新的值并显示在页面上。
这样咱们就实现了一个最简单的计数器状态共享,可是每次都本身写太麻烦了,能够设计一个简单易用,立马能够上手的通用工具使用。
设计一个通用工具须要看应用场景和通用模型,面向对象是一个不错的选择。关于更多细节能够看 Piex Store 核心概念 来了解,咱们看一下怎么实现:
export abstract class Store { state = {}; setters = []; setState(newState) { this.state = newState; this.setters.forEach(setState => setState(this.state)); } } export function useStore(store) { let [, setState] = useState(store.state); store.setters.push(setState); return store; } 复制代码
因为 JS 不支持继承,因此这段代码用 TS 实现,去掉空行,短短 13 行代码就实现了一个状态管理:
state
对应上例中的 _count
,存储全局状态;setters
对应上例中的 _setters
,收集 setState
;setState
方法用来更新组件;useStore
则用来收集依赖;具体怎么使用呢?以下:
class CounterStore extends Store { state = { count: 0, } increment() { this.setState({ count: this.state.count + 1, }) } decrement() { this.setState({ count: this.state.count + 1, }) } } const counterStore = new CounterStore(); const Counter = () => { const store = useStore(counterStore); return ( <article> <p>{store.state.count}</p> <button onClick={store.increment}>Increment</button> <button onClick={store.decrement}>Decrement</button> </article> ) } const App = () => { return ( <div> <Counter /> <Counter /> </div> ) } 复制代码
这样,全部用到 counterStore
的地方均可以共享 counterStore.state
的变量,还能够经过对象方法来更新 state
并同步到其它组件。
固然,这只是一个简单的 demo,不少东西都没有作,如 setters
收集的 setState
依赖在组件卸载时释放;全量更新状态太麻烦,仅部分更新就能够了,还有数据变动检测等等。
这些确定不是 13 行代码能够实现的了,若是感兴趣能够看 Piex Store,基于上述原理实现的状态管理工具,完美支持 TS 类型推断,听从 React 设计哲学,支持中间件,可使用 Redux DevTools 观察状态变动。
因为 Piex Store 还处于襁褓状态,不少地方还有须要完善的地方,还请你们多多支持。