原文地址javascript
对于想要跳过文章直接看结果的人,我已经把我写的内容制做成了一个库:use-simple-state,无任何依赖(除了依赖 react ),只有3kb,至关轻量。html
const state = {};
export const getState = () => state;
export const setState = nextState => {
state = nextState;
};
复制代码
一个全局可用的用于展示咱们应用状态的值:state;java
读取状态的能力:getState;react
更新状态的能力:setState。git
context
API,它可让数据在整个 react 应用可用。context
有两部分:provider (生产者) 和 consumer (消费者),provider 就像它的名字所说的那样,给应用提供 context
(data 数据),消费者意指当咱们读取 context
时,咱们就是消费者。context
:若是说 props
是显式的传送数据,那么 context
就是隐式的传送数据。如今咱们知道了须要哪些工具,如今只要把它们合在一块儿就能够了。咱们准备建立一个上下文环境来存放全局状态,而后把它的 provider 包裹在一个有状态组件中,而后用 provider 来管理状态。github
首先,咱们使用 React.createContext
来建立上下文,它能够给咱们提供 provider 和 consumer。web
import { createContext } from 'react';
const { Provider, Consumer } = createContext();
复制代码
接下来咱们须要用有状态组件包裹咱们的 provider,利用它进行应用状态的管理。咱们也应该把 consumer 导出为一个更加准确的名称。express
import React, { Component, createContext } from 'react';
const { Provider, Consumer } = createContext();
export const StateConsumer = Consumer;
export class StateProvider extends Component {
static defaultProps = {
state: {}
};
state = this.props.state;
render () {
return (
<Provider value={this.state}> {this.props.children} </Provider>
);
}
}
复制代码
在上面的例子中,StateProvider
是接收一个 state
来做为初始状态的组件,而且使组件树中当前组件下面的任何组件均可以访问到这个属性。若是没有提供 state
,默认会有一个空对象代替。redux
用咱们的 StateProvider
包裹住根组件:api
import { StateProvider } from './stateContext';
import MyApp from './MyApp';
const initialState = {
count: 0
};
export default function Root () {
return (
<StateProvider state={initialState}> <MyApp /> </StateProvider>
);
}
复制代码
在咱们完成上述代码以后,就能够做为一个消费者从 MyApp
的任何地方得到应用的状态。在这里咱们会初始化咱们的状态为一个有一个 count
属性的对象,因此不管何时咱们想要当即获取应用的状态,咱们就能够从这里得到。
消费者使用 render 属性 来传递上下文数据,咱们能够经过下面的一个函数做为 StateConsumer
的子组件的例子来查看。state
参数传递给函数用以展示咱们应用的当前状态,做为咱们的初始状态,state.count
为 0.
import { StateConsumer } from './stateContext';
export default function SomeCount () {
return (
<StateConsumer> {state => ( <p> Count: {state.count} </p> )} </StateConsumer>
);
}
复制代码
关于 StateConsumer
咱们须要知道的很重要一点是在上下文中它会自动订阅状态的改变,所以当咱们的状态改变后会从新渲染以显示更新。这就是消费者的默认行为,咱们暂时还没作可以用到这个特性的功能。
目前为止咱们已经能够读取应用的状态,以及在状态改变时自动更新。如今咱们须要一种更新状态的方法,为了作到这一点咱们仅仅只须要在 StateProvider
里面更新状态。
你以前可能已经注意到了,咱们给 StateProvider
传递了一个 state
属性,也就是以后会传递给组件的 state
属性。咱们将使用 react 内置的 this.setState
来更新:
export class StateProvider extends Component {
static defaultProps = {
state: {}
};
state = this.props.state;
render () {
return (
<Provider value={{ state: this.state, setState: this.setState.bind(this) }}> {this.props.children} </Provider>
);
}
复制代码
继续保持简单的风格,咱们只给上下文传递 this.setState
,这意味着咱们须要稍微改变咱们的上下文传值,不仅是传递 this.state
,咱们如今同时传递 state
和 setState
。
当咱们用 StateConsumer
时能够用解构赋值获取 state
和 setState
,而后咱们就能够读写咱们的状态对象了:
export default function SomeCount () {
return (
<StateConsumer> {({ state, setState }) => ( <> <p> Count: {state.count} </p> <button onClick={() => setState({ count: state.count + 1 })}> + 1 </button> <button onClick={() => setState({ count: state.count - 1 })}> - 1 </button> </> )} </StateConsumer>
);
}
复制代码
有一点要注意的是因为咱们传递了 react 内置的 this.setState
做为咱们的 setState
方法,新增的属性将会和已有的状态合并。这意味着若是咱们有 count
之外的第二个属性,它也会被自动保存。
如今咱们的做品已经能够用在真实项目中了(尽管还不是颇有效率)。对 react 开发者来讲应该会以为 API 很熟悉,因为使用了内置的工具所以咱们没有引用任何新的依赖项。假如以前以为状态管理有点神奇,但愿如今咱们多少可以了解它内部的结构。
熟悉 redux 的人可能会注意到咱们的解决方案缺乏一些特性:
- 没有内置的处理反作用的方法,你须要经过 redux 中间件来作这件事
- 咱们的
setState
依赖 react 默认的this.setState
来处理咱们的状态更新逻辑,当使用内联方式更新复杂状态时将可能引起混乱,同时也没有内置的方法来复用状态更新逻辑,也就是 redux reducer 提供的功能。- 也没有办法处理异步的操做,一般由 redux thunk 或者 redux saga等库来提供解决办法。
- 最关键的是,咱们没办法让消费者只订阅部分状态,这意味着只要状态的任何部分更新都会让每一个消费者更新。
为了解决这些问题,咱们模仿 redux 来应用咱们本身的 actions,reducers,和 middleware。咱们也会为异步 actions 增长内在支持。以后咱们将会让消费者只监听状态内的子状态的改变。最后咱们来看看如何重构咱们的代码以使用新的 hooks api。
免责声明:接下来的内容只是为了让你更容易理解文章,我强烈推荐你阅读 redux 官方完整的介绍。
若是你已经很是了解 redux,那你能够跳过这部分。
如你所见,这就是单向数据流,从咱们的 reducers 接收到状态改变以后,触发 actions,数据不会回传,也不会在应用的不一样部分来回流动。
说的更详细一点:
首先,咱们触发一个描述改变状态的 action,例如 dispatch({ type: INCREMENT_BY_ONE })
来加1,同咱们以前不一样,以前咱们是经过 setState({ count: count + 1 })
来直接改变状态。
action 随后进入咱们的中间件,redux 中间件是可选的,用于处理 action 反作用,并将结果返回给 action,例如,假如在 action 到达 reducer 以前触发一个 SIGN_OUT
的 action 用于从本地存储里删除全部用户数据。若是你熟悉的话,这有些相似于 express 中间件的概念。
最后,咱们的 action 到达了接收它的 reducer,伴随而来的还有数据,而后利用它和已有的状态合并生成一个新的状态。让咱们触发一个叫作 ADD
的 action,同时把咱们想发送过去增长到状态的值也发送过去(叫作 payload )。咱们的 reducer 会查找叫作 ADD
的 action,当它发现后就会将 payload 里面的值和咱们现有的状态里的值加到一块儿并返回新的状态。
reducer 的函数以下所示:
(state, action) => nextState
复制代码
reducer 应当只是处理 state 和 action ,虽然简单却很强大。关键是要知道 reducer 应当永远是纯函数,这样它们的结果就永远是肯定的。
如今咱们已通过了几个 redux app 的关键部分,咱们须要修改 app 来模仿一些相似的行为。首先,咱们须要一些 actions 和触发它们的方法。
咱们的 action 会使用 action 建立器来建立,它们其实就是能生成 action 的简单函数,action 建立器使得测试,复用,传递 payload 数据更加简单,咱们也会建立一些 action type,其实就是字符串常量,为了让他们能够被 reducer 复用,所以咱们把它存储到变量里:
// Action types
const ADD_ONE = 'ADD_ONE';
const ADD_N = 'ADD_N';
// Actions
export const addOne = () => ({ type: ADD_ONE });
export const addN = amount => ({ type: ADD_N, payload: amount });
复制代码
如今咱们来作一个 dispatch
的占位符函数,咱们的占位符只是一个空函数,将会被用于替换上下文中的 setState
函数,咱们一会再回到这儿,由于咱们还没作接收 action 的 reducer 呢。
export class Provider extends React.PureComponent {
static defaultProps = {
state: {}
};
state = this.props.state;
_dispatch = action => {};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 复制代码
如今咱们已经有了一些 action,只须要一些 reducer 来接收就行了。回到以前的 reducer 函数标记,它只是一个关于 action 和 state 的纯函数:
(state, action) => nextState
复制代码
知道了这个,咱们只须要传递组件的状态,而后在 reducer 里触发 action。对 reducer 来讲,咱们只想要一个对应上面标记的函数数组。咱们之因此使用一个数组是由于可使用数组的 Array.reduce
方法来迭代数组,最终生成咱们的新状态:
export class Provider extends React.PureComponent {
static defaultProps = {
state: {},
reducers: []
};
state = this.props.state;
_dispatch = action => {
const { reducers } = this.props;
const nextState = reducers.reduce((state, reducer) => {
return reducer(state, action) || state;
}, this.state);
this.setState(nextState);
};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 复制代码
如你所见,咱们所作的就是使用 reducer 来计算并得到新状态,而后就像以前所作的,咱们调用 this.setState
来更新 StateProvider
组件的状态。
如今咱们只须要一个实际的 reducer:
function countReducer ({ count, ...state }, { type, payload }) {
switch (type) {
case ADD_N:
return { ...state, count: count + payload };
case ADD_ONE:
return { ...state, count: count + 1 };
}
}
复制代码
咱们的 reducer 只是检查传入的 action.type
,而后假如匹配到以后将会更新相对应的状态,不然就会在通过 switch
判断语句以后返回函数默认的 undefined
。咱们的 reducer 和 redux 的 reducer 的一个重要的区别在当咱们不想更新状态时,通常状况下咱们会由于未匹配到 action type 而返回一个falsy 值,而 redux 则会返回未变化的状态。
而后把咱们的 reducer 传进 StateProvider
:
export default function Root () {
return (
<StateProvider state={initialState} reducers={[countReducer]}> <MyApp /> </StateProvider>
);
}
复制代码
如今咱们终于能够触发一些 action,而后就会观察到相对应的状态更新:
export default function SomeCount () {
return (
<StateConsumer> {({ state, dispatch }) => ( <> <p> Count: {state.count} </p> <button onClick={() => dispatch(addOne())}> + 1 </button> <button onClick={() => dispatch(addN(5))}> + 5 </button> <button onClick={() => dispatch(addN(10))}> + 10 </button> </> )} </StateConsumer>
);
复制代码
如今咱们的做品已经跟 redux 比较像了,只须要再增长一个处理反作用的方法就能够。为了达到这个目的,咱们须要容许用户传递中间件函数,这样当 action 被触发时就会被调用了。
咱们也想让中间件函数帮助咱们处理状态更新,所以假如返回的 null
就不会被 action 传递给 reducer。redux 的处理稍微不一样,在 redux 中间件你须要手动传递 action 到下一个紧邻的中间件,假如没有使用 redux 的 next
函数来传递,action 将不会到达 reducer,并且状态也不会更新。
如今让咱们写一个简单的中间件,咱们想经过它来寻找 ADD_N
action,若是它找到了那就应当把 payload
和当前状态里面的 count
加和并输出,可是阻止实际状态的更新。
function countMiddleware ({ type, payload }, { count }) {
if (type === ADD_N) {
console.log(`${payload} + ${count} = ${payload + count}`);
return null;
}
}
复制代码
跟咱们的 reducer 相似,咱们会将中间件用数组传进咱们的 StateProvider
。
export default function Root () {
return (
<StateProvider state={initialState} reducers={[countReducer]} middleware={[countMiddleware]} > <MyApp /> </StateProvider>
);
}
复制代码
最终咱们会调用全部全部中间件,而后根据返回的结果决定是否应当阻止更新。因为咱们传进了一个数组,然而咱们须要的是一个单个值,所以咱们准备使用 Array.reduce
来得到咱们的值。跟 reducer 相似,咱们也会迭代数组依次调用每一个函数,而后将结果赋值给一个变量 continueUpdate
。
因为中间件被认为是一个高级特性,所以咱们不想它变成强制性的,所以若是没有在StateProvider
里面找到 middleware
属性,咱们会将 continueUpdate
置为默认的 undefined
。咱们也会增长一个 middleware
数组来做默认属性,这样的话 middleware.reduce
就不会由于没传东西而抛出错误。
export class StateProvider extends React.PureComponent {
static defaultProps = {
state: {},
reducers: [],
middleware: []
};
state = this.props.state;
_dispatch = action => {
const { reducers, middleware } = this.props;
const continueUpdate = middleware.reduce((result, middleware) => {
return result !== null ? middleware(action, this.state) : result;
}, undefined);
if (continueUpdate !== null) {
const nextState = reducers.reduce((state, reducer) => {
return reducer(state, action) || state;
}, this.state);
this.setState(nextState);
}
};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 复制代码
如你所见在第13行,咱们会查看中间件函数的返回值。若是返回 null
咱们就会跳过剩下的中间件函数,continueUpdate
将为 null
,意味着咱们会中断更新。
由于咱们想让咱们的状态管理器对真实生产环境有用,因此咱们须要增长对异步 action 的支持,这意味着咱们将能够处理像网络请求相似案例的通用任务。咱们借鉴下 Redux Thunk ,由于它的 API 很简单,直观并且有效。
咱们所要作的就是检查是否有为被调用的函数被传递到 dispatch,若是找到的话咱们就会在传递 dispatch
和 state
时调用函数,这样就能够给用户所写的异步 action 执行的机会。拿这个受权 action 做为例子来看下:
const logIn = (email, password) => async dispatch => {
dispatch({ type: 'LOG_IN_REQUEST' });
try {
const user = api.authenticateUser(email, password);
dispatch({ type: 'LOG_IN_SUCCESS', payload: user });
catch (error) {
dispatch({ type: 'LOG_IN_ERROR', payload: error });
}
};
复制代码
在上面的例子中咱们写了一个叫作 logIn
的 action 建立器,不是返回一个对象,它返回一个接收 dispatch
的函数,这可让用户在一个异步 API 请求的前面和后面触发异步 action,根据 API 不一样的返回结果触发不一样的 action,这里咱们在发生错误时发送一个错误 action。
作到这一点只须要在 StateProvider
里的 _dispatch
方法里检查 action
的类型是否是 function
:
export class StateProvider extends React.PureComponent {
static defaultProps = {
state: {},
reducers: [],
middleware: []
};
state = this.props.state;
_dispatch = action => {
if (typeof action === 'function') {
return action(this._dispatch, this.state);
}
const { reducers, middleware } = this.props;
const continueUpdate = middleware.reduce((result, middleware) => {
return result !== null ? middleware(action, this.state) : result;
}, undefined);
if (continueUpdate !== null) {
const nextState = reducers.reduce((state, reducer) => {
return reducer(state, action) || state;
}, this.state);
this.setState(nextState);
}
};
render () {
return (
<StateContext.Provider value={{ state: this.state, dispatch: this._dispatch }}> {this.props.children} </StateContext.Provider> ); } } 复制代码
这里须要注意两点:咱们调用 action
为函数,传入 this.state
,这样用户能够访问异步 action 中已有的状态,咱们也会返回函数调用的结果,容许开发者从他们的异步 action 中得到一个返回值从而开启更多的可能性,例如从 dispatch
触发的 promise 链。
Redux 的一个常常被忽视的必要特性是它能在必须时才会对组件从新渲染(或者更准确的说是 React-Redux — react 跟 redux 的绑定)。为了作到这一点,它使用了 connect
高阶组件,它提供了一个映射函数 — mapStateToProps
— 仅仅只在关联的 mapStateToProps
的输出改变时(只映射从如今开始的状态)才会触发组件的从新渲染。若是不这样的话,那么每次状态更新都会让组件使用 connect 来订阅存储改变而后从新渲染。
想一想咱们须要作的,咱们须要一种方法来存储 mapState
前面的输出,这样咱们就能够比较二者看看有没有差别来决定咱们是否须要继续向前和从新渲染咱们的组件。为了作到这一点咱们须要使用一种叫作记忆化的进程,跟咱们这行的许多事情同样,对于一个想到简单的进程来讲,这是一个重要的词,尤为是咱们可使用 React.Component
来存储咱们状态的子状态,而后仅在咱们检测到 mapState
的输出改变以后再更新。
接下来咱们须要一种可以跳过没必要要的组件更新的方法。react 提供了一个生命周期方法 shouldComponentUpdate
可让咱们达到目的。它将接收到的 props 和 state 当作参数来让咱们同现有的 props 和 state 进行比较,若是咱们返回 true
那么更新继续,若是返回 false
那么 react 将会跳过渲染。
class ConnectState extends React.Component {
state = this.props.mapState(this.props.state);
static getDerivedStateFromProps (nextProps, nextState) {}
shouldComponentUpdate (nextProps) {
if (!Object.keys(this.state).length) {
this.setState(this.props.mapDispatch(this.props.state));
return true;
}
console.log({
s: this.state,
nextState: nextProps.mapState(nextProps.state),
state: this.props.mapState(this.state)
});
return false;
}
render () {
return this.props.children({ state: this.props.state, dispatch: this.props.dispatch });
}
}
export function StateConsumer ({ mapState, mapDispatch, children }) {
return (
<StateContext.Consumer> {({ state, dispatch }) => ( <ConnectState state={state} dispatch={dispatch} mapState={mapState} mapDispatch={mapDispatch}> {children} </ConnectState> )} </StateContext.Consumer> ); } 复制代码
上面只是对咱们接下来要作的事情的概述。它有了因此主要的部分:从咱们的 context 接收更新,它使用了 getDerivedStateFromProps
和 shouldComponentUpdate
,它也接收一个 render 属性来做为子组件,就像默认的消费者同样。咱们也会经过使用传递的 mapState
函数来初始化咱们的消费者初始状态。
如今这样的话,shouldComponentUpdate
将只会在接收到第一次状态更新以后渲染一次。以后它会记录传进的状态和现有的状态,而后返回 false
,阻止任何更新。
上面的解决方案中在 shouldComponentUpdate
内部也调用了 this.setState
,而咱们都知道 this.setState
老是会触发从新渲染。因为咱们也会从 shouldComponentUpdate
里返回 true
,这会产生一次额外的从新渲染,因此为了解决这个问题,咱们将使用生命周期 getDerivedStateFromProps
来获取咱们的状态,而后咱们再使用 shouldComponentUpdate
来决定基于咱们获取的状态是否继续更新进程。
若是咱们检查控制台也能够看到全局的状态更新,同时咱们的组件阻止任何更新到 this.state
对象以致组件跳过更新:
如今咱们知道了如何阻止没必要要的更新,咱们还须要一个能够智能的决定咱们的消费者什么时候应当更新的方法。若是咱们想要递归循环传进来的 state 对象来查看每一个属性来看状态是否有改变,可是这对于帮助咱们理解有帮助却对性能不利。咱们没办法知道传进来的 state 对象层级有多深或者多复杂,若是条件永远不知足,那么递归函数将会无限期的执行下去,所以咱们准备限制咱们比较的做用域。
跟 redux 相似,咱们准备使用一个浅比较函数。浅在这里意味着咱们在比较咱们的对象是否相等的属性的层级,意味着咱们只会比较一层。所以咱们将会检查咱们每一个新状态的顶层属性是否等于咱们现有状态的同名属性,若是同名属性不存在,或者它们的值不一样,咱们将会继续渲染,不然咱们就认为咱们的状态时相同的,而后阻止渲染。
function shallowCompare (state, nextState) {
if ((typeof state !== 'object' || state === null || typeof nextState !== 'object' || nextState === null)) return false;
return Object.entries(nextState).reduce((shouldUpdate, [key, value]) => state[key] !== value ? true : shouldUpdate, false);
}
复制代码
首先咱们从检查是否两个 state 都是对象开始,若是不是那么咱们就跳过渲染。在初始检查以后咱们把现有的状态转化为一个键值对的数组,并经过将数组减小为单个布尔值来检查每一个属性的值与传进来的 state 对象的值。
这是困难的部分,如今咱们想用咱们的 shallowCompare
函数,实际上只是调用并检查结果。若是它返回 true
,咱们就返回 true
来容许组件从新渲染,不然咱们就返回 false
来跳过更新(而后咱们得到的状态被放弃掉)。咱们也想在 mapDispatch
存在的时候调用它。
class ConnectState extends React.Component {
state = {};
static getDerivedStateFromProps ({ state, mapState = s => s }) {
return mapState(state);
}
shouldComponentUpdate (nextProps, nextState) {
return shallowCompare(this.state, nextState);
}
render () {
return this.props.children({
state: this.state,
dispatch: this.props.mapDispatch ? this.props.mapDispatch(this.props.dispatch) : this.props.dispatch
});
}
}
复制代码
最后咱们须要传递一个 mapState
函数让咱们消费者以只匹配咱们的部分状态,这样咱们就会将它做为一个属性传给咱们的 StateConsumer
:
return (
<StateConsumer mapState={state => ({ greeting: state.greeting })} mapDispatch={dispatch => dispatch}> // ... 复制代码
如今咱们只订阅 greeting
里面的改变,所以假如咱们更新了组件里的 count
将会被咱们的全局状态改变所忽略而且避免了一次从新渲染。
若是你还没听过它,它正在快速变成 react 的下一个大特性。这里有一段来自官方描述的简单解释:
Hooks 是一个让你没必要写 class 就可使用 state 和 react 其余特性的新功能。
hooks 提供给咱们高阶组件的全部能力,以及更加清晰和直观的 API 来渲染属性。
咱们来看一个使用基本的 useState
钩子的例子来看看它们是如何工做的:
import React, { useState } from 'react';
function Counter () {
const [count, setCount] = useState(0);
return (
<> <span> Count: {count} </span> <button onClick={() => setCount(count + 1)}> Increment </button> </> ); } 复制代码
在上面的例子中,咱们经过给 useState
传递一个 0 来初始化新状态,它会返回咱们的状态:count
,以及一个更新函数 setCount
。若是你之前没见过的话,可能会奇怪 useState
是如何不在每次渲染时初始化,这是由于 react 在内部处理了,所以咱们无需担忧这一点。
让咱们暂时先忘掉中间件和异步 action,用 useReducer
钩子来从新应用咱们的 provider,就像咱们正在作的同样,除了将 action 触发到一个得到新状态的 reducer,它就像 useState
同样工做。
知道了这个,咱们只须要将 reducer 的逻辑从老的 StateProvider
拷贝到新的函数组件 StateProvider
里就能够了:
export function StateProvider ({ state: initialState, reducers, middleware, children }) {
const [state, dispatch] = useReducer((state, action) => {
return reducers.reduce((state, reducer) => reducer(state, action) || state, state);
}, initialState);
return (
<StateContext.Provider value={{ state, dispatch }}> {children} </StateContext.Provider> ); } 复制代码
能够如此的简单,可是当咱们想要保持简单时,咱们仍然没有彻底掌握 hooks 的全部能力。咱们也可使用 hooks 来把咱们的 StateConsumer
换为咱们本身的钩子,咱们能够经过包裹 useContext
钩子来作到:
const StateContent = createContext();
export function useStore () {
const { state, dispatch } = useContext(StateContext);
return [state, dispatch];
}
复制代码
尽管以前当咱们建立咱们的上下文时使用了解构的 Provider
和 Consumer
,可是此次咱们会将它存到咱们传递到 useContext
单个变量从而让咱们可不用 Consumer
就能够接入上下文。咱们也将它命名为咱们本身的 useStore
钩子,由于 useState
是一个默认的钩子。
接下来咱们来简单地重构下咱们消费上下文数据的方法:
export default function SomeCount () {
const [state, dispatch] = useStore();
return (
<>
<p>
Count: {state.count}
</p>
<button onClick={() => dispatch(addOne())}>
+ 1
</button>
<button onClick={() => dispatch(addN(5))}>
+ 5
</button>
<button onClick={() => dispatch(addN(10))}>
+ 10
</button>
</>
);
}
复制代码
但愿这些例子能让你感觉到 hooks 是如何的直观、简单和有效。咱们减小了所需的代码数量,而且给了咱们一个友好、简单的 API 供使用。
咱们也想让咱们的中间件和内置的异步 action 从新开始工做。为了作到这一点,咱们将咱们的 useReducer
包裹进一个自定义钩子,在咱们的 StateProvider
中被特殊的使用,而后简单地重用咱们老的状态组件的逻辑就行了。
export function useStateProvider ({ initialState, reducers, middleware = [] }) {
const [state, _dispatch] = useReducer((state, action) => {
return reducers.reduce((state, reducer) => reducer(state, action) || state, state);
}, initialState);
function dispatch (action) {
if (typeof action === 'function') {
return action(dispatch, state);
}
const continueUpdate = middleware.reduce((result, middleware) => {
return result !== null ? middleware(action, state) : result;
}, undefined);
if (continueUpdate !== null) {
_dispatch(action);
}
}
return { state, dispatch };
}
复制代码
在咱们的老的解决方案中,咱们想让中间件是可选的,因此咱们添加了一个空数组做为默认值,一样地此次咱们也使用一个默认的参数来替换默认属性。相似于咱们老的 dispatch 函数,咱们调用了中间件,而后若是 continueUpdate !== null
咱们就继续更新状态。咱们也不会改变处理异步 action 的方式。
最终,咱们将 useStateProvider
的结果和它的参数到咱们的 provider,这咱们以前没怎么考虑:
export function StateProvider ({ state: initialState, reducers, middleware, children }) {
return (
<StateContext.Provider value={useStateProvider({ initialState, reducers, middleware })}> {children} </StateContext.Provider> ); } 复制代码
完结!
在开始的时候提到过,我写了一个库,Use Simple State,看完这篇文章以后,你能够在个人 github页面看到,我已经使用 hooks 最终实现了,包括几个新增的功能。