阅读 612

TypeScript + React最佳实践-第二节:Redux类型化(1)

前言

TypeScript + React 类型安全三件套:Component、Redux、和Service类型化。

上一节的 Component 类型化里有两个 TODO,mapStateToProps(state: IRootState)IRootStateDispatchPropsactions,这两个类型是来自 Redux,故而本节的主要内容是 Redux 类型化。

Redux类型化

涉及以下几个子集:

  • state 类型化
  • actions 类型化
    • action & reducer 类型化

state 类型化

可以将所有子 state 的类型合并到一起,得到 IRootState

    interface IStateA {}
    interface IStateB {}
    
    ...
    
    type IRootState = IStateA & IStateA & ...;
复制代码

也可以将所有子 state 的值合并到一起,推断得到 IRootState

    interface IStateA {}
    const stateA: IStateA = {}
    interface IStateB {}
    const stateB: IStateB = {}
    
    ...
    
    const rootState =  { ...stateA, ...stateB, ... };
    type IRootState =  typeof rootState;
复制代码

actions 类型化

同样,可以将相关的 action 值合并到一起——这里,如果单个 action* 是类型化的,则 actions 也是类型化的:

    const actions = {
        doA,
        doB,
        doC,
        ...
    };
    export default actions;
复制代码

action & reducer 类型化

我们可以引入 typesafe-actionsredux-actions 来实现这个目的。

    npm i typesafe-actions redux-actions;
复制代码
创建类型化的 action

redux-actions 也提供了一个 createAction 接口,由于TS类型推断机制,会造成 action 的 payload 类型错误,因此选择 typesafe-actions

    import { createAction } from 'typesafe-actions';

    const DOA = 'DOA';
    interface IParamsA { ... }
    const doA = (paramsA: IParamsA) => createAction(DOA, paramsA);
复制代码
创建类型化的 reducer

我们可以引入 redux-actionshandleActions 来创建 reducer, 以避免写 switch case:

const reducerA = handleActions(
    {
        [doA]: (state: IStateA, action: ReturnType<typeof doA>) => {
            return {
                ...state,
                ...action.payload
            };
        }
    },
  stateA
)
复制代码

这样似乎就可以了——但是真的很繁琐:

  • 传统的state & action Type & action & reducer 1v1v1v1,写起来太繁琐了。
  • 合并所有子 state,子 action,维护起来就更繁琐了。

Model

所幸传统 redux 繁琐的问题,已经早有人意识到并且实现了解决方案,这就是 dvajs:

dvajs model

dvajs 提供了一种清晰的组织 state、actions & reducers、effects 的数据结构,示例:

app.model({
    namespace: 'xxx',
    state: [],
    reducers: {
        doSomething(state, action) => {}
    },
    effects: {
        *doSomethingAsync(action, utils){}
    }
})
复制代码

结构里 reducers 和 effects 内的方法会被转换为同名的 action, 并通过 ${namespace}/${keyName} 取值的 action type 将 action 和 reducer 自动关联起来。

类型化 model

基于 dvajs 的数据结构设计,有了 tkit-model——类型化的 model。

引入依赖:

    npm i tkit-model 
复制代码

后续会迁移到 @tefe/model 下,并可以通过 @tefe/redux-model@tefe/use-model 分别引用 redux model 和 use-model hooks。

示例:

// userModel.ts
import createModel, { CM } from 'tkit-model';
interface State { data: number[] }
const state: State = { data: [] };

const userModel = CM({
    namespace: 'user',
    state
    reducer: {
        doSomething: (state, action: { payload: number }) {
            // ok
            state.data.push(action.payload);
            // type error
            state.data.push('类型错误');
        }
    },
    effects: {
        *doSomethingAsync({ tPut }, action: { paylong: number }) {
        // ok
        yield tPut(model.actions.doSomething, action.payload);
        // type error
        yield tPut(model.actions.doSomething, '类型错误');
    }
    }
});

const { userModelState, reducers, actions, sagas } = userModel;
复制代码

将 model 的 state、reducers、actions、sagas 注册到对应的全局 redux 上。

// component.tsx
type DispatchProps = ReturnType<typeof mapDispatchToProps>;
type Props = OwnProps & DispatchProps;

function Cp(props: Props) {
    return (
        <>
            <button onclick={() => props.actions.doSomethingAsync(1)}>click</button>
            <button onclick={() => props.actions.doSomethingAsync('类型错误')}>click</button>
        </>
    )
}

function mapDispatchToProps(dispatch: Dispatch) {
    return {
        actions: bindActionCreators(actions, dispatch)
    };
}
复制代码

TODO

自动合并各子 actions、state、reducers等物料的 CLI,解决合并繁琐的问题。

文章分类
前端
文章标签