@ivliu/use-enhancer

基于useReducer的dispatch增强器

Usage no npm install needed!

<script type="module">
  import ivliuUseEnhancer from 'https://cdn.skypack.dev/@ivliu/use-enhancer';
</script>

README

use-enhancer

use-enhancer如其名,是一个简单且强大的hooks。它基于中间件模式(没错,它类似于redux中间件模式,但它是基于双向链表而非数组,为什么是链表?因为我想做更多我认为还不错的point)来增强useReducer的dispatch,增强后可以方便的执行异步action或者记录日志等操作。我会提供thunk(类似于redux-thunk),all(类似于promise race),race(类似于promise all)中间件作为例子。

安装

yarn add @ivliu/use-enhancer # or npm install @ivliu/use-enhancer --save

中间件

我通过中间件的形式来增强dispatch,它编写需要遵循一定的规则(类似于redux中间件形式),一眼看去就是一个复杂的闭包运用。

类型声明

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

export type TMiddleware = <R extends Reducer<any, any>>(
  storeRef: MutableRefObject<ReducerState<R>>, 
  dispatch: MutableRefObject<Dispatch<ReducerAction<R>>>
) => (next: TNext) => (action: ReducerAction<R> | ReducerAction<R>[]) => Promise<void>;

export type TNext = <R extends Reducer<any, any>>(...actions?: ReducerAction<R>[]) => Promise<void>;

它有两种形式,一种有action和一种无action版,你可通过闭包访问到storeRef(通过storeRef.current访问store,不要解构,会丢失响应式),dispatch(通过dispatchRef.current即可访问到dispatch,不要解构,会丢失响应式),以及next函数。

import { TMiddleware } from './type';

export const thunk: TMiddleware = () => next => async action => {
  if(!action) {
    await next();
    return;
  }
  if(typeof action === 'function') {
    action = await action();
  }
  await next(action);
}

以上是内置thunk的定义,大家可以做个参考。

派生action

我们执行dispatch的时候会传入一个action(它可以是个对象或者函数),我把它定义为源action,会被传入每个middleware的action形参位置。同时你还可以基于当前action计算出另一个action(你如在thunk内,我们根据函数式action计算出对象action),我把它定义为派生action,把它作为入参传入next函数即可,我会通过单向链表把它们链接起来。当然你不需要担心action链会造成过度渲染的问题,我通过batch函数优化了。

举个例子吧

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 (<p>So handsome, you actually use use-enhancer.</p>)
};

另外您还可以把项目clone下来,实际跑一下例子

git clone 
cd use-enhancer
yarn # or npm i
npm start
cd example
yarn # or npm i
npm start

然后打开http://localhost:1234即可看到效果

计划

  1. 为了更好的执行副作用,我打算把中间件执行分为三个部分,分别是capture,target(已启用),bubble,类似于DOM事件流,其中capture阶段主要是真正dispatch执行前的操作,比如ajax请求等;target阶段是真正的dispatch阶段,不能打断;bubble阶段是dispatch之后的阶段,比如执行之后重新设置页面title,执行时机我打算放在useEffect执行,即页面绘制完毕后异步执行。这也是我采用双向链表的原因,可以方便的打断(敬请期待)。
  2. 多个dispatch支持

最后

希望大家可以多多提意见,多多star,多多pr。