React Hook的用法: Contex + Reducer(二)

110 阅读6分钟

React Hook

简述

通过上篇文章我们知道使用 State Hook 和 Effect Hook 可以让函数组件也能够具有自己的状态和在组件的各个阶段提供钩子暴露给开发者使用(点击我查看 State 和 Effect Hook)

实际在业务开发过程中我们往往会结合Redux来实现组件的传值和全局转态管理,这里就来分享一下使用Hook来实现组件的传值以及状态管理。

Context Hook

Context Hook可以让我们实现夸层级去传递组件的参数,从而避免了参数从根组件一层一层传递下去(爷爷->父亲->儿子)的现象,这样代码更加扁平,传递路径更加明了,也更加容易维护和扩展。 看下面这个设计的Demo,根组件共享给子组件两个变量,子组件通过Context Hook来获取根组件传递过来的值,同时在这个子组件中又引入了另一个子组件,而这个孙子辈的子组件也可使用Context Hook来直接获取根组件传递过来的值。

基础关键的代码: 需要在根组件中创建一个共享的组件 StoreContext, 并需要初始化一个值,如下:

export const StoreContext = createContext({});

将需要共享给子组件的数据放在上述所创建的上下文中并作为根组件去包裹所需要共享的子组件。如下:

<StoreContext.Provider value={StoreObj}>
    {/* 在下面这个子组件中去获取该父组件共享出去的值 */}
   <ChildContext />
</StoreContext.Provider>

在这样包裹之后,凡是在 StoreContext.Provider 组件内部的子组件(不管是直接子组件还是间接子组件即孙子组件)都可以通过 useContext 这个钩子直接使用根组件传递过来的值 在对应的子组件中先从React中引入useContext钩子,并从根组件中引入暴露出来的 StoreContext;就可直接在子组件中获取根组件的共享数据。 关键代码如下:

import  { useContext } from 'react';
// 获取根组件传递过来的数据
const shareObj = useContext(StoreContext)

效果如下: 在这里插入图片描述 在线Demo

Reducer Hook

上述部分解决了组件之间层层传递数据的问题,在实际开发应用中我们还需要实现应用状态的统一管理,比如使用Redux来实现应用状态的统一管理。现在了解下如果使用Hook的话该如何实现。 比如现在一个场景,一个父组件A包含两个子组件a1,a2(a1,a2是兄弟关系)。其中a1,a2都使用Context Hook来获取A组件的数据,这时候a1组件触发了某种操作修改了根组件的数据源,那这个时候a2组件也要同步更新数据。 上述场景在业务开发中经常遇见,比如我们在一个子组件中控制当前页面的语言显示,那么切换语言之后,应用内所有的其他组件也应当更新当前的语言。 这个例子设计的是在根组件中提供了子组件所用的语言包,在按钮组件中可以切换当前展示的语言,按钮组件切换语言之后另一个展示文案的组件的语言也需要同步更新。 关键点代码如下(zhLang, enLang表当前语言包的对象):

export const LangContext = createContext(zhLang);
export const ZH_LANG = "ZH_LANG";
export const EN_LANG = "EN_LANG";

第一行是创建一个上下文,共享当前的语言包,默认是中文,第二行第三行实际上是action type 用于区分当前的语言是那种。

const reducer = (state, action) => {
    switch (action.type) {
        case ZH_LANG:
            return zhLang;
        case EN_LANG:
            return enLang
        default:
            return zhLang
    }
}

创建一个reducer 用于去处理不同的action type 业务逻辑,在这里就是根据子组件发出的action type 来决定当前返回那种语言包。其中第一个参数 state其实是这个应用中的上一个状态,而action就是子组件dispatch方法中传递的参数,一般在action中有一个type用于标记业务处理,如果还需要传递额外的参数的话一般添加在action.payload之中。

const [lang, dispatch] = useReducer(reducer, zhLang);

在根组件中将处理业务的reducer和默认的初始值(中文包)传入useReducer中,这个函数放回的值使用数组的结构赋值得到当前的语言包和dispatch,其中lang就是当前需要共享给子组件的数据,同样的dispatch方法也要共享到子组件中,这样子组件可以按需引用并dispatch某一个特定的 action 当然也可以附加额外的一些业务参数。

 <LangContext.Provider value={{lang, dispatch}}>
	<div className="reducer-container">
       <div className="des">{lang.rootTxt}</div>
       <ChangeLantBtns />
       <ShowLangComponent />
	 </div>
 </LangContext.Provider>

更改语言的子组件关键代码:

import { LangContext, ZH_LANG, EN_LANG } from './../ReducerHooks'

先要在子组件中引入 LangContext 结合 useContext 钩子就可以使用根组件传递过来的参数, ZH_LANG, EN_LANG这两个的引入就好比Redux 中的action type引入一样,用于业务判断之后dispatch出去的动作

const { lang, dispatch } = useContext(LangContext);
<Button type="primary" onClick={()=>{dispatch({type:EN_LANG,payload:"other Params"})}}>{lang.en}</Button>

第一行是使用解构赋值的方式得到 LangContext.Provider 组件传递过来的lang, dispatch。这样子组件就可以使用当前的语言包并展示数据(${lang.en}) 当我们点击按钮的时候,就会触发 dispatch方法,该方法接受的参数其实是会传递给到事先注册的reducer方法之中的action。

对于第二个子组件只是要使用父组件传递过来的数据的话,那这个套路就和上一部分使用useContext一样。 关键点代码:

import { LangContext } from './../ReducerHooks;

引入根组件创建的上下文变量

const { lang } = useContext(LangContext) ;

useContext 的固定写法,也是老套路,这样就获取了父组件传递过来的共享语言包。

<div>{lang.txt1}</div>

获取lang之后,就可以直接使用父组件共享出的当前语言包中的语言描述了。 这样就实现了之前的功能,在这里父组件共享语言包给子组件,其中按钮组组件可以切换语言,切换语言之后 ShowLangComponent 组件中的语言也会变化。整个过程是使用 useContext hook来实现父组件数据的共享和 uesReducer hook 管理应用的状态(这里的状态就是共享给子组件的语言包是英文状态还是中文状态) 整体效果如下: 在这里插入图片描述 在线Demo

总结

  1. 使用 uesContext 解决了组件层层传值的问题
  2. 使用uesReducer 解决了应用状态的统一管理
  3. uesContext + uesReducer 可以实现Redux的效果而且没有Redux那么重,整个过程清晰明了,数据流向清晰,易扩展易维护