共计 4868 个字符,预计需要花费 13 分钟才能阅读完成。
You Might Not Need Redux.
—— Dan Abramov
然而咱们能够用 useReducer 和 useContext ~
后面说的话:
useContext 能够实现状态共享,useReducer 能够实现犹如 redux 状态管理器 dispatch 的性能。
这样一来,咱们就能够拿这两个 hook 来实现一个简略的状态管理器了。
如果再退出 ts 呢,咱们能够想到的是本人定义的诸多 type
,通过 ts 加编辑器的反对,在咱们眼前出现的那种愉悦感 ~
在我的项目中,咱们可能会有多个状态须要共享,咱们可能会在登录后异步申请获取用户信息,而后在其余页面会用到这个用户信息 …。
那就让咱们就用这两个 hook 举个例子吧:
(这两个 hook 还不理解的小伙伴,能够看上一篇文章介绍,点我点我 )
实现异步获取用户信息的相干文件
userInfo/index.declare.ts
export interface IState {
id?: string;
name?: string;
isFetching?: boolean;
failure?: boolean;
message?: string;
}
type TType =
| "ASYNC_SET_USER_INFO"
| "FETCHING_START"
| "FETCHING_DONE"
| "FETCHING_FAILURE";
export interface IAction {
type: TType;
payload?: IState;
}
这个文件这里把它提取进去,申明了根本的 state、type 的束缚与 action,参数用 payload 来接管
userInfo/index.tsx
import React, {useReducer, createContext, useContext} from "react";
import {IState, IAction} from "./index.declare";
// 初始化状态
const initialState: IState = {
id: "",
name: "",
isFetching: false,
failure: false,
message: ""
};
// 创立一个 context,并初始化值
const context: React.Context<{
state: IState;
dispatch?: React.Dispatch<IAction>;
}> = createContext({state: initialState});
// reducer
const reducer: React.Reducer<IState, IAction> = (
state,
{type, payload}
): IState => {switch (type) {
case "ASYNC_SET_USER_INFO": {const { id, name, message} = payload!;
return {...state, id, name, message};
}
case "FETCHING_START": {return { ...state, failure: false, isFetching: true};
}
case "FETCHING_DONE": {return { ...state, isFetching: false};
}
case "FETCHING_FAILURE": {return { id: "", name:"", failure: true, message: payload?.message};
}
default:
throw new Error();}
};
/**
* mock:模仿了申请接口的异步期待
*/
const request = (id: string): Promise<any> => {return new Promise((resolve, reject) => {setTimeout(() => {if (id === "998") {resolve({ id: "998", name: "liming", message: "获取用户胜利"});
} else {reject(` 找不到 id 为 ${id} 的用户 `);
}
}, 1000);
});
};
/**
* dispatch 异步 / 同步 高阶函数
*/
const dispatchHO = (dispatch: React.Dispatch<IAction>) => {return async ({ type, payload}: IAction) => {if (type.indexOf("ASYNC") !== -1) {
dispatch({type: "FETCHING_START"});
try {const { id, name, message} = await request(payload!.id!);
dispatch({type, payload: { id, name, message} });
} catch (err) {dispatch({ type: "FETCHING_FAILURE", payload: { message: err} });
}
dispatch({type: "FETCHING_DONE"});
} else {dispatch({ type, payload});
}
};
};
/**
* ProviderHOC 高阶组件
*/
export const ProviderHOC = (WrappedComponent: React.FC) => {const Comp: React.FC = (props) => {const [state, dispatch] = useReducer(reducer, initialState);
return (<context.Provider value={{ state, dispatch: dispatchHO(dispatch) }}>
<WrappedComponent {...props} />
</context.Provider>
);
};
return Comp;
};
/**
* 封装 useContext
*/
export const useContextAlias = () => {const { state, dispatch} = useContext(context);
return [state, dispatch] as [IState, React.Dispatch<IAction>];
};
解释:
- request 办法只是一个模仿接口申请期待而已。
- 在实在的业务场景中,咱们会异步申请用户信息,所以实现 异步 action 的外围代码就在 dispatchHO 办法,这是一个高阶函数,dispatch 作为参数。在咱们发动异步申请时,咱们须要对一些状态进行扭转,如申请前,申请胜利,申请失败 …,咱们须要把它封装到一个“大 dispatch”里。约定 type 有“ASYNC”的状况下才会触发这个特地的“大 dispatch”。
- ProviderHOC 是一个高阶组件,个别咱们会用这种写法,来共享状态,如:
<context.Provider value={obj}>
<App />
</context.Provider>;
然而这里咱们用高阶组件的形式,让咱们在对 root 组件包裹的时候能够更灵便,请急躁持续往下看。
- useContextAlias 办法 是对 useContext 的再封装,这里我把它转换成咱们比拟理解的 useReducer 写法,如:
const [state, dispatch] = useContext();
我的项目中文件目录构造可能是这样子的
咱们能够看到 reducers 专门用来放一些 reducer 模块,userInfo、userList…
reducers/index.ts 作为一个 main 文件,咱们来看看外面的实现:
import React from "react";
import {
ProviderHOC as ProviderHOCUserList,
useContextAlias as useContextUserList
} from "./userList";
import {
ProviderHOC as ProviderHOCUserInfo,
useContextAlias as useContextUserInfo
} from "./userInfo";
/**
* 组合各个 provider
*/
const compose = (...providers: any[]) => (root: any) =>
providers.reverse().reduce((prev, next) => next(prev), root);
const arr = [ProviderHOCUserList, ProviderHOCUserInfo];
const providers = (root: React.FC) => compose(...arr)(root);
export {useContextUserList, useContextUserInfo};
export default providers;
解释:
- compose 办法是组合各个 provider 的外围办法,咱们引入了各个模块裸露进去的办法 ProviderHOC 而后再进行组合他们,这使得咱们能够很灵便的去增加更多的 provider,而不必要手动的在 root 组件上进行包裹,在 App 中咱们就能够这样,如:
App.tsx
import React from "react";
import "./styles.css";
import providers from "./reducers";
import UseReducerDemo from "./userReducer.demo";
const App = () => {
return (
<div className="App">
<UseReducerDemo />
</div>
);
};
export default providers(App);
- 咱们把 import 进来的 useContextUserList, useContextUserInfo,别名之后再次导出,在其余页面,只有针对的引入想要用的 context 即可,如:
userReducer.demo.tsx
import React from "react";
import {useContextUserInfo, useContextUserList} from "./reducers";
const Index: React.FC = () => {const [userInfo, dispatchUserInfo] = useContextUserInfo();
const [userList, dispatchUserList] = useContextUserList();
return (
<div className="demo">
userInfo:
<p> 状态:{userInfo.isFetching ? "正在加载中..." : "加载结束"}</p>
<p>id:{userInfo.id}</p>
<p>name:{userInfo.name}</p>
<p>message:{userInfo.message}</p>
<button
disabled={userInfo.isFetching}
onClick={() => {
dispatchUserInfo({
type: "ASYNC_SET_USER_INFO",
payload: {id: "998"}
});
}}
>
异步获取用户信息 id="998"
</button>
<button
disabled={userInfo.isFetching}
onClick={() => {
dispatchUserInfo({
type: "ASYNC_SET_USER_INFO",
payload: {id: "1"}
});
}}
>
异步获取用户信息 id="1"
</button>
);
};
export default Index;
总结
咱们在做我的项目的时候,这两个 hook 能够用来做很笨重的 redux,咱们还能够本人实现异步 action。再加上 ts,让咱们在其余页面书写 dispatch 有一种稳重感 ~,用 compose 办法组合各个高阶组件,让咱们更加灵便的共享各个状态。
所以,连忙点我查看残缺例子