“都 1202 年了怎么还有人在用 Redux”——这大略不少人看到这篇文章的第一反馈。首先先表明一下,这篇文章并不探讨是不是应该应用 Redux,这是一个比拟大的话题,应该独自水一篇。而且社区曾经存在许许多多的探讨了,你总能从几篇高赞的文章中找到一些优缺点的比照图,而后联合你我的项目的场景最终作出决定。咱们来轻易举几个团队应用 Redux 的起因。首先是易懂,Redux 被人吐槽很多的可能是写法繁琐,然而在繁琐写法的背地就没有那么多黑科技了,非常容易排查问题。另外,Redux 实质是对逻辑解决形式提出了规范范式,并且搭配得给到了一组实际标准,有助于放弃我的项目代码书写格调与组织形式的一致性,这点在多人合作开发的我的项目外面尤为重要。其余的长处就不在此赘述啦。

这时候就有同学可能要问了,你讲 Redux,那和 hooks 又有啥子关系呢。家喻户晓,在 React 团队推出 Hooks 这个概念后不久,Redux 也更新了对应的 API 来反对。Hooks 的实质是对逻辑的封装以及逻辑与 UI 代码的解耦。有了 Hooks 的加持可能让咱们的 Redux React 我的项目更加简洁、易懂、扩展性更强。而且 Hooks API 在 Redux 的最佳实际倡议中目前是 Level 2 的强烈推荐应用级别。他领有更简洁的表达方式,更洁净的 React 节点数,更敌对的 typescript 反对。

具体 Redux 相干的 API 怎么用,这里不做介绍,能够间接跳转官网文档进行理解。上面咱们会从一个利用场景来具体讲一讲,他们是怎么帮忙咱们更好地组织代码的。其中的局部工程级别代码来自于 react-boilerplate 的我的项目模版,它在动静加载问题上提供了不少帮忙。

封装案例

在开发大型 React 利用的时候,动静懒加载代码永远是咱们我的项目架构中的必选项。代码的拆分、动静援用等,工程化工具都曾经帮咱们实现了。咱们更须要关注的是,动静引入与解除挂载等操作时额定要做什么,以及这个工作如何尽量少的裸露给我的项目开发者。后面说过了,Hooks 最弱小的能力在于逻辑的封装,这里当然也就要借助他的力量了。

这里咱们以 Reducer 作为例子来讲,其余中间件,例如 Saga 等都能够类推,如果须要能够后续再把相应的代码一并贴出来。咱们把整个封装分为三层:外围实现、可组合封装、对开发者裸露封装。上面咱们按程序一一解说。(具体实现中我都会默认带上蕴含 connected router 的实现,不便须要抄代码的能够间接用)

外围实现

这里的代码实现的是如何为一个 store 挂载与解除挂载拆分后的各个 Reducer 的逻辑。

// 本段代码齐全来自于 react-boilerplate 我的项目import { combineReducers } from 'redux';import { connectRouter } from 'connected-react-router';import invariant from 'invariant';import { isEmpty, isFunction, isString } from 'lodash';import history from '@/utils/history';import checkStore from './checkStore'; // 做类型平安检测的,不必关怀function createReducer(injectedReducers = {}) {  return history => combineReducers({    router: connectRouter(history),    ...injectedReducers,  });}export function injectReducerFactory(store, isValid) {  return function injectReducer(key, reducer) {    if (!isValid) checkStore(store);    invariant(      isString(key) && !isEmpty(key) && isFunction(reducer),      '(src/utils...) injectReducer: Expected `reducer` to be a reducer function',    );    if (      Reflect.has(store.injectedReducers, key)      && store.injectedReducers[key] === reducer    ) return;    store.injectedReducers[key] = reducer; // eslint-disable-line no-param-reassign    store.replaceReducer(createReducer(store.injectedReducers)(history));  };}export default function getInjectors(store) {  checkStore(store);  return {    injectReducer: injectReducerFactory(store, true),  };}

这段有个点比拟非凡,须要讲一下。你可能会发现,这外面基本没有解除挂载的局部。这是因为 reducer 比拟非凡,他并不会产生副作用,并且因为目前提供的办法是通过整个替换的形式去挂载新的 Reducer,所以并没有什么必要去独自做解除挂载。在解决其余中间件的挂载时,特地是那些存在副作用的(例如 redux-saga),咱们须要对应地实现一个解除挂载的 eject 办法。

OK,那么当初咱们曾经能够通过 getInjectors 办法为整个我的项目提供一个 injectReducer 注入 Reducer 的能力了(同时可能蕴含 eject 办法)。下一步就是怎么调度这个能力。

可组合的封装

这里,咱们心愿通过一个自定义的 hooks,能够容许开发者为一个组件申明某一个 命名空间 的 reducer 与其生命周期统一地进行挂载与解除挂载。开发者只须要传入 reducer 的命名空间与 reducer 实现,并将这个 hooks 放到相应的组件逻辑中即可。

import React from 'react';import { ReactReduxContext } from 'react-redux';// 这是咱们在上一步实现的 injector 工厂,通过他来产出一个与固定 store 绑定的 injectReducer 函数import getInjectors from './reducerInjectors';const useInjectReducer = ({ key, reducer }) => {  // 须要从 Redux 的 context 中获取到以后利用的全局 store 实例  const context = React.useContext(ReactReduxContext);    // 为了模仿 constructor 的运行机会  const initFlagRef = React.useRef(false);  if (!initFlagRef.current) {    initFlagRef.current = true;    getInjectors(context.store).injectReducer(key, reducer);  }    // 如果须要退出 eject 的逻辑,则能够应用这样的写法。相似于为以后组件减少一个 willUnmount 的生命周期逻辑。  // React.useEffect(() => (() => {  //   const injectors = getInjectors(context.store);  //   injectors.ejectReducer(key);  // }), []);};export { useInjectReducer };

useInjectReducer 这个 Hooks 帮忙咱们解决了何时去挂载,怎么挂载等问题,咱们最终只须要通知他 挂载什么 就能够了。通过这层封装,能够发现咱们进一步收敛了关注点。到这一步为止,咱们都是提供了一个我的项目级别的公共办法。在下一步中,咱们会提供一个对立的写法,在具体的开发过程中去应用,进一步做封装收敛。

在进入下一步之前,咱们先简略解释一下下面的逻辑。逻辑通过正文分为了三段(第三段在 reducer 场景下没用到),第一段咱们通过以后组件所处的 redux 上下文,拿到了 store 的援用,第二段与第三段咱们别离让组件在 初始化 和 销毁前 执行挂载与解除挂载的操作。通过一个 initFlagRef 为 functional 的组件模仿结构器的生命周期(如果有更好的实现计划欢送指教),因为如果在挂载之后再 inject 的话,会在第一次渲染时取不到对应 store 的内容。

对开发者裸露封装

在实现专用办法的封装之后,咱们下一步思考的就是如何用更简略的形式,为咱们的模块挂载 store 。依照上面的形式,开发者不必关怀任何货色,只需一句话就能够实现挂载,也不必提供额定的参数。如果同时有 reducer、saga 或其余中间件内容,也能够一起打包搞定。

import {   useInjectReducer,   // useInjectSaga,} from '@/utils/store';import actions from './actions';import constants from './constants';import reducer from './reducer';// import saga from './saga';const namespace = constants.namespace;const useSubStore = () => {  useInjectReducer({ key: namespace, reducer });  // useInjectSaga({ key: namespace, saga });};export {  namespace,  actions,  constants,  useSubStore,};

理论应用范例:

import React from 'react';import {  useSubStore,} from './store';export default function Page() {  useSubStore();    return <div />;};

具体的数据和逻辑咱们也能够封装成几个 Hooks ,例如咱们须要提供一个数组数据简略操作,咱们只关怀 增加 和 数量,就能够封装一个 Hooks,这样理论应用方只须要关怀 增加 和 数量 这两个因素,不必关怀 redux 的具体实现形式了。

import { useMemo, useCallback } from 'react';import { useDispatch, useSelector } from 'react-redux';import {  actions, constants, namespace,} from './store';export function useItemList() {  const dispatch = useDispatch();  const list = useSelector(state => state[namespace].itemList);  // 这只是范例!  const count = useMemo(() => list.length, [list]);  const add = useCallback((item) => dispatch(actions.addItem(item)), []);    return [count, add];}

上面咱们批改一下应用的中央:

import React from 'react';import {  useSubStore,} from './store';import { useItemList } from './useItemList';export default function Page() {  useSubStore();  const [count, add] = useItemList();    return <div onClick={() => add({})}>{count}</div>;};

通过这样一种拆分形式,store 的定义,store 的应用逻辑,业务侧三者都只关注本人必须关注的局部,任何一方改变都能够尽量少地引起变更。

可复用的 Hooks

那咱们进一步思考一下,以前咱们可能一个页面对应一个 store。通过 Hooks 进行拆分后,咱们更不便从性能层面去拆分 store,store 的逻辑也会更为清晰。与 store 的交互被封装成了 Hooks 之后也能够很快在多个展现层被应用。这在简单 B 端工作台场景下会展现出很大的价值。案例会有点长,当前有工夫能够再补上。

回顾

看完下面的例子,置信聪慧的读者曾经晓得我想表白的问题了。通过联合 Redux + Hooks,标准化了定义代码,对逻辑、调用、定义三者肯定水平上进行理解耦。通过简化的 API,缩小了逻辑的了解老本,缩小了后续保护的复杂度,肯定水平上还能够达到复用。不论是相较于过来的 Redux 接入计划,还是相较于单纯应用 Hooks,都有着其独特的劣势。特地实用于逻辑绝对简单的工作台场景。(而且我很喜爱 Saga 的设计思路,能用起来就很爽)。

OK,收。这次以一个简略的例子,稍稍展现了一下在 Hooks 大环境下 Redux 与其产生的化学反应。次要想展现的是依赖 Hooks 的逻辑可封装能力的一种设计思路,Redux 黑的同学们不要过多纠结与这个选型,萝卜青菜各有所爱。

心愿这个系列能持续写下去 :)

作者:ES2049 / armslave00

文章可随便转载,但请保留此原文链接。
十分欢送有激情的你退出 ES2049 Studio,简历请发送至 caijun.hcj@alibaba-inc.com 。