你可能并不需要redux

751 阅读3分钟

如果你不知道是否需要 Redux,那就是不需要它。简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。《redux入门教程 - 阮一峰》

前言

我们回想下我们为什么准备用redux,是否我们的交互已经复杂到难以维护,是否存在复杂的共享状态,是否存在复杂的组件间交互,如果并没有以上的复杂度,我们也许并非需要redux。我个人也在业务中用过redux,但是我的业务大多是些轻交互的H5页面,redux沦为一种强规范。另外请相信我,有的程序员特别擅长把强规范写成一团糟。

useReducer

Hook是 React 16.8 的新增特性。它可以让我们在不编写 class 的情况下使用 state 以及其他的 React 特性。其中useReducer引起了我的注意,满满的亲切感铺面。回到前言,我的业务逻辑实际上并没有那么复杂,但是我还想利用redux强规范的特性来把视图和逻辑做很好的归置,useReducer显然很适合。

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

如上,我可以把逻辑组织为reducr函数,Counter组件只负责视图层的展示,从而实现了很好的视图逻辑分离。

问题

但是我们的业务不只是前端的自娱自乐,我们还需要做诸如打日志、请求接口等含副作用的操作。这时候单纯的useReducer就无能为力了,因为他只能处理一些同步的操作。我们开始怀念redux的中间件(ps:原来我只是馋redux的中间件)。

useReducer + middleware = useEnhancer

既然我们馋的是中间件,那我就简单粗暴把它们结合起来。先看下基础使用

import { React, FC useReducer } from 'react';
import useEnhancer from '@ivliu/use-enhancer';

const fakeThunk = () => next => async action => {
  if(typeof action === 'function') {
    action = await action();
  }
  await next(action);
}

const fakeLog = () => next => async () => {
  console.log('sorry, i am a fake log.');
  await next();
}

const reducer = (state, action) {
  const { type, payload } = action;
  switch(type) {
    case 'ASYNC_TYPE':
      return { ...state, ...payload };
    default: 
    return state;
  }
}

const App: FC = () => {
  const [state, rawDispatch] = useReducer(reducer, {});
  const dispatch = useEnhancer(
    state,
    rawDispatch,
    fakeThunk,
    fakeLog,
  );
  useEffect(() => {
    dispatch(async () => {
      await new Promise(r => setTimeout(() => r(), 3000));
      return ({ type: 'ASYNC_TYPE', payload: { /* some data */ }})
    })
  }, [])
  return (<h3 onClick={() => dispatch({ type: 'SOME_TYPE', payload: {} })}>{ /* state */}</h3>)
};

看到这里大家应该明白我为什么起名字叫enhancer,他其实就是一个基于中间件的dispatch增强器,我们需要什么能力都可以写个中间件实现,比如我内置的thunk插件,我们可以利用它非常方便的实现异步操作。

派生Action

有些时候,我们的操作是为了执行下一个或多个操作,这听起来比较拗口。举个例子,我们的接口请求操作实际没有什么意义,根据接口成功或失败执行的对应的操作才有意义,我把这种根据某种操作产生的另一种操作成为派生action,它会被挂载到内部的action链表尾部,等所有派生action收集完毕后统一执行。

中间件

export type TMiddlewareWithoutAction = <R extends ReducerWithoutAction<any>>(
  store: ReducerStateWithoutAction<R>, 
  dispatch: DispatchWithoutAction
) => (next: TNext) => () => Promise<void>;

export type TMiddleware = <R extends Reducer<any, any>>(
  store: ReducerState<R>, 
  Dispatch: Dispatch<ReducerState<R>>
) => (next: TNext) => (action: ReducerAction<R>) => Promise<void>;

export type TNext = <R extends Reducer<any, any>>(action?: ReducerAction<R>) => Promise<void>;

以上是中间件的类型定义,我们可以在闭包内部访问到当前store,dispatch,next,以及action。

双向链表

type TLink = {  callback: TLinkCallback;  pending: Promise<any> | null;  prev: TLink;  next: TLink;} | null;

其中中间件通过双向链表的形式组织,可以充分利用小块内存。

后话

我觉得我文笔一般,这也是我第一次在掘金发帖,希望大家可以不吝建议。

github地址 github.com/IVLIU/use-e…