美文网首页
⌛️ 结合 Context API 和 useReducer H

⌛️ 结合 Context API 和 useReducer H

作者: BubbleM | 来源:发表于2020-08-11 15:25 被阅读0次

    背景:React 的单向数据流模式导致状态只能以 props 的形式从父组件一级一级的传递到子组件,在大中型应用中如果涉及深层嵌套、或者说任意两个组件之间这样跨度较大的通信,我们一般是直接通过全局事件总线(Event Bus)或者引入 Redux 来解决。

    React 深层嵌套的组件间通信方式

    场景:组件A和组件C都需要展示400手机虚拟号信息,组件B中有一个按钮,点击后会重新调接口获取手机号信息,同时需要更新组件A和组件C的展示。

    1. 全局事件总线
      我们可以通过对 event 的订阅和发布来进行通信。
    • 全局安装 events 第三方库 npm i events --save-dev
    • 创建事件总线并导出:
    import { EventEmitter } from 'events';
    export const eventBus = new EventEmitter();
    
    • 监听:组件A和组件C中监听事件
    const [phoneNum, setPhoneNum] = useState();
    useEffect(()=>{
      eventBus.addListener('getPhone', phoneNum => setPhoneNum(phoneNum));
      return () => {
        eventBus.removeListener('getPhone', () => {})
      }
    }, [])
    
    • 派发:组件B中点击按钮后派发事件
    const handleClick = function(){
      eventBus.emit('getPhone', Math.random());
    }
    
    <button onClick={() => handleClick()}>更新</button>
    
    1. Redux
      Redux 来源于 Flux 并借鉴来 Elm 的思想。2015 年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
    Redux数据流图.png

    View 中事件通过 actionGreator 函数调用 dispatch 发布 action 到 reducers,然后各自的 reducer 根据 action 类型(action.type)来按需更新整个应用的 state。

    • State:表示Model的状态数据
    • Action:改变State的唯一途径。无论是从UI事件、网络回调、还是WebSocket等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。(action必须带有type属性指明具体行为)
    • dispatch函数:用于触发 action 的函数,action是改变State的唯一途径,但它只描述了一个行为,而dispatch可以看作是触发这个行为的方式,而Reducer则描述如何改变数据。
    • Reducer:接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值。通过actions中传入的值,与当前reducers中的值进行运算获得新的值(也就是新的state)

    👍 优点:

    • 这种数据流的控制可以让应用更可控,以及让逻辑更清晰。

    🔧 缺点:

    • 概念太多,并且reducer,saga,action都是分离的(分文件)
    • 编辑成本高,需要在 reducer、saga、action之间来回切换
    • 不便于组织业务模型,比如我们写了一个userlist之后,要写一个productlist,需要复制很多文件。
    • saga 书写太复杂,每监听一个 action 都需要走 fork -> watcher -> worker 的流程
    1. Context API
      随着 React16.3 版本的发布,在深层嵌套这个场景下,有了一个新的答案:使用 Context API
      ⚠️ 注意:React很早就支持context,只是官方不建议使用,因为是一个实验性的API,可能会被改变。但从React 16.3 开始,Context API得到了升级,不再作为不稳定的实验性能力存在,因此可以放心使用。
      🤔️ Q:Context API是干嘛的?
      😯 A:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。主要用来解决跨组件传参泛滥的问题(prop drilling)。
      当我们想要跨 N 个层级传递某个数据时,逐层传递props就会变得非常繁琐,而且还会带来不必要的数据更新(比如说一些全局性质的数据,用户名、用户权限等)。
      Context 面向这类场景,提供了一种在组件之间共享此类值的方式,它允许我们不必显式地通过组件树的逐层传递 props。
      强力推荐:React新Context API在前端状态管理的实践

    Model层封装

    1. 入口文件处理。由于新的 context api 传递过程中不会被 shouldComponentUpdate 阻断,只需要在 Provider 里监听 store 的变化。
    import { escContext, action, useMyReducer } from '../models/store.ts';
    // ...
    const [state, dispatch] = useMyReducer();
    return(
      <escContext.Provider value={{state, dispatch}}>
        <A/>
        <B/>
        <C/>
      </escContext.Provider>
    )
    
    1. Model 层 store.ts 封装,这里通过自定义 Hooks useMyReducer 实现异步action的处理。
    import { useReducer } from 'react';
    
    export const initialState:TEscState = {
        phone: ''
    }
    export const escContext = React.createContext<TMixStateAndDispatch>({state: initialState});
    
    export const types = {
        SET_PHONE: 'SET_PHONE',
        GET_PHONE: 'GET_PHONE',
    }
    export const action = {
        setPhone: (phone: number|string) => {
            return {
                type: types.SET_PHONE,
                phone: phone
            }
        },
        getPhone: (directShowFlag?: boolean|string) => {
            return (dispatch: React.Dispatch<any>, state) => {
                getJsonp('http://mock.test.url....', res => {
                    if (res.status == 1) {
                        dispatch(action.setPhone(res.call_num));
                    }
                });
            }
        }
    }
    
    export const reducer = (state:TEscState, action:TAction) => {
        switch(action.type){
            case types.SET_PHONE:
                return {...state, phone: action.phone}
            default:
                throw new Error('Unexpected action');
        }
    }
    
    // 自定义Hooks用于处理异步action
    export const useMyReducer = function():[TEscState, React.Dispatch<any>]{
        const [state, dispatch] = useReducer(reducer, initialState);
    
        function myDispatch(action){
            if(typeof action === 'function'){
                return action(dispatch, state);
            }else{
                dispatch(action);
            }
        }
        return [state, myDispatch]
    }
    
    1. 子组件A、B、C处理
    import { escContext, action } from '../../models/store.ts';
    const {state, dispatch} = useContext(escContext);
    
    <div>{state.phone}</div>  // state.phone 直接获取数据用于展示
    <button onClick={()=>dispatch(action.getPhone())}></button>
    

    小结

    本次结合 Context API 和 useReducer Hooks 封装 Model层,统一数据源处理,业务和展示分离,将业务逻辑沉淀在Model层中,便于后期维护。

    相关文章

      网友评论

          本文标题:⌛️ 结合 Context API 和 useReducer H

          本文链接:https://www.haomeiwen.com/subject/cxvedktx.html