之前的教程 12 篇
明天,咱们将对它进行摸索,并开发一个自定义的Hook来治理全局状态--这是一个比Redux更容易应用的办法,而且比Context API更有性能。
钩子的基础知识
如果你曾经相熟React Hooks,你能够跳过这部分。
useState()
在Hooks之前,性能组件没有状态。当初,有了 "useState()",咱们能够做到这一点。
它的工作原理是返回一个数组。上述数组的第一项是一个变量,它提供了对状态值的拜访。第二项是一个函数,它更新组件的状态以反映DOM上的新值。
useEffect()
类组件应用生命周期办法治理副作用,如componentDidMount()
。useEffect()
函数能够让你在函数组件中执行副作用。
默认状况下,成果会在每次实现渲染后运行。然而,你能够抉择只在某些值发生变化时才启动它,传递一个变量数组作为第二个可选参数。
为了失去和componentDidMount()
一样的后果,咱们能够发送一个空数组。晓得空数组永远不会扭转,所以成果只会运行一次。
咱们能够看到,Hooks状态的工作原理和类组件状态齐全一样。组件的每个实例都有本人的状态。
为了工作在组件之间共享状态的解决方案,咱们将创立一个自定义的Hook。
咱们的想法是创立一个监听器数组和惟一一个状态对象。每当一个组件扭转状态时,所有被订阅的组件都会被触发setState()
函数并失去更新。
咱们能够通过在咱们的自定义Hook外面调用useState()
来实现。然而,咱们不返回setState()
函数,而是将其增加到一个监听器数组中,并返回一个更新状态对象和运行所有监听器函数的函数。
等等,这不是应该让我的生存更轻松吗?
是的,我创立了一个NPM包,封装了所有这些逻辑。
use-global-hook
在不到1kb的范畴内应用钩子对react进行简略的状态治理。
www.npmjs.com
你不须要在每个我的项目中重写这个自定义钩子。如果你只是想跳过并应用最终的解决方案,你能够通过运行以下命令轻松地将其增加到你的我的项目中。
npm install -s use-global-hook。
你能够通过软件包文档中的例子来学习如何应用它。然而,从当初开始,咱们将重点介绍它的工作原理。
第一版
import { useState, useEffect } from 'react';let listeners = [];let state = { counter: 0 };const setState = (newState) => { state = { ...state, ...newState }; listeners.forEach((listener) => { listener(state); });};const useCustom = () => { const newListener = useState()[1]; useEffect(() => { listeners.push(newListener); }, []); return [state, setState];};export default useCustom;
第一个版本曾经能够共享状态。你能够在你的应用程序中增加任意多的Counter组件,并且它将领有雷同的全局状态。
import React from 'react';import useCustom from './customHook';const Counter = () => { const [globalState, setGlobalState] = useCustom(); const add1Global = () => { const newCounterValue = globalState.counter + 1; setGlobalState({ counter: newCounterValue }); }; return ( <div> <p> counter: {globalState.counter} </p> <button type="button" onClick={add1Global}> +1 to global </button> </div> );};export default Counter;
但咱们能够做得更好。在第一个版本中我不喜爱的中央。
- 我想在组件卸载时从数组中移除监听器。
- 我想让它更通用,这样咱们就能够在其余我的项目中应用。
- 我想通过参数设置一个 "initialState"。
- 我想应用更多面向函数的编程。
在组件卸载前调用一个函数。
咱们理解到,用空数组调用 "useEffect(function,[])",与 "componentDidMount() "有雷同的用处。然而,如果在第一个参数中应用的函数返回另一个函数,那么这个第二个函数将在组件被卸载之前被启动。和componentWillUnmount()
齐全一样。
这是将组件从监听器数组中移除的最佳地位。
第二版
const useCustom = () => { const newListener = useState()[1]; useEffect(() => { // Called just after component mount listeners.push(newListener); return () => { // Called just before the component unmount listeners = listeners.filter(listener => listener !== newListener); }; }, []); return [state, setState];};
除了这最初的批改,咱们还将。
- 将React设置为参数,不再导入它。
- 不导出customHook,而是导出一个函数,依据
initialState
参数返回一个新的customHook。 - 创立一个 "store "对象,其中蕴含 "state "值和 "setState() "函数。
- 替换
setState()
和useCustom()
中的箭头函数为惯例函数,这样咱们就能够有一个上下文来将store
绑定到this
。
function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); });}function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { // Called just after component mount this.listeners.push(newListener); return () => { // Called just before the component unmount this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.setState];}const useGlobalHook = (React, initialState) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); return useCustom.bind(store, React);};export default useGlobalHook;
因为咱们当初有了一个比拟通用的Hook,所以咱们要在store文件中设置它。
import React from 'react';import useGlobalHook from './useGlobalHook';const initialState = { counter: 0 };const useGlobal = useGlobalHook(React, initialState);export default useGlobal;
将口头与组件离开
如果你已经应用过简单的状态治理库,你就会晓得间接从组件中操作全局状态不是最好的主见。
最好的办法是通过创立操纵状态的动作来拆散业务逻辑。出于这个起因,我心愿咱们解决方案的最初一个版本不给组件拜访setState()
函数,而是给一组动作。
为了解决这个问题,咱们的useGlobalHook(React, initialState, actions)
函数将接管一个action
对象作为第三个参数。对于这一点,我想补充一些货色。
- Actions将能够拜访
store
对象。因而,动作能够通过store.state
读取状态,通过store.setState()
写入状态,甚至应用state.actions
调用其余动作。 - 对于组织,动作对象能够蕴含其余动作的子对象。因而,你可能有一个
actions.addToCounter(a amount)
或者一个蕴含所有计数器动作的子对象,用actions.counter.add(a amount)
调用。
最终版本
以下文件是NPM包use-global-hook
中的理论文件。
function setState(newState) { this.state = { ...this.state, ...newState }; this.listeners.forEach((listener) => { listener(this.state); });}function useCustom(React) { const newListener = React.useState()[1]; React.useEffect(() => { this.listeners.push(newListener); return () => { this.listeners = this.listeners.filter(listener => listener !== newListener); }; }, []); return [this.state, this.actions];}function associateActions(store, actions) { const associatedActions = {}; Object.keys(actions).forEach((key) => { if (typeof actions[key] === 'function') { associatedActions[key] = actions[key].bind(null, store); } if (typeof actions[key] === 'object') { associatedActions[key] = associateActions(store, actions[key]); } }); return associatedActions;}const useGlobalHook = (React, initialState, actions) => { const store = { state: initialState, listeners: [] }; store.setState = setState.bind(store); store.actions = associateActions(store, actions); return useCustom.bind(store, React);};export default useGlobalHook;