使用React Hooks创建类似Redux的全局存储

哈Ha! 我向您介绍了Ramsay撰写的文章“使用React Hooks建立类似Redux的全球商店”的翻译。


假设我写了一篇有趣的文章介绍,现在我们可以直接看一些有趣的事情。 简而言之,我们将
使用useReduceruseContext创建一个自定义的React钩子,该钩子提供对类似于Redux的全局存储库的访问。


我不会以任何方式假定此解决方案完全等同于Redux,因为我敢肯定它不是。 说“类似Redux”是指
您将使用dispatchactions更新存储库,这将使存储库的状态发生变异并返回该变异状态的新副本。
如果您从未使用过Redux,请假装不阅读本段。


钩子


让我们开始创建一个上下文( 以下称为Context ),该上下文将包含我们的状态( 以下简称 state )和一个调度函数( 以下称为dispatch )。 我们还将创建useStore函数,其行为类似于钩子。


// store/useStore.js import React, { createContext, useReducer, useContext } from "react"; //     const initialState = {} const StoreContext = createContext(initialState); // useStore    React       export const useStore = store => { const { state, dispatch } = useContext(StoreContext); return { state, dispatch }; }; 

由于所有内容都存储在React Context中 ,因此您需要创建一个Provider
我们有一个状态对象和一个调度函数。 Provider是我们使用useReducer的地方


 // store/useStore.js ... const StoreContext = createContext(initialState); export const StoreProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> {children} </StoreContext.Provider> ); }; ... 

我们使用useReducer来获取状态调度 。 实际上,这正是useReducer所做的。 接下来,我们将状态传递给Provider
现在我们可以使用<Provider />包装任何React组件,并且该组件可以使用useStore与存储库进行交互。


我们还没有创建减速器 。 这将是我们的下一步。


 // store/useStore.js ... const StoreContext = createContext(initialState); //    actions,     state const Actions = {}; // reducer   ,  action    dispatch // action.type -  ,     Actions //   update   state      const reducer = (state, action) => { const act = Actions[action.type]; const update = act(state); return { ...state, ...update }; }; ... 

我非常喜欢将操作状态分为逻辑组,例如:您可能需要监视计数器的状态(计数器实现的经典示例)或用户的状态(用户是否已登录系统或他的个人偏好)。
在某些组件中,您可能需要访问这两种状态,因此将它们存储在单个全局存储库中的想法很有意义。 我们可以将操作分成逻辑组,例如userActionscountActions ,这使管理它们变得更加容易。


让我们在商店文件夹中创建countActions.jsuserActions.js文件


 // store/countActions.js export const countInitialState = { count: 0 }; export const countActions = { increment: state => ({ count: state.count + 1 }), decrement: state => ({ count: state.count - 1 }) }; 

 // store/userActions.js export const userInitialState = { user: { loggedIn: false } }; export const userActions = { login: state => { return { user: { loggedIn: true } }; }, logout: state => { return { user: { loggedIn: false } }; } }; 

在这两个文件中,我们都导出了initialState ,因为我们希望稍后将它们在useStore.js文件中组合为单个initialState对象。
我们还导出一个Actions对象,该对象提供状态突变的功能。 请注意,我们不会返回新的状态对象,因为我们希望这发生在useStore.js文件的reducer中


现在,我们将其全部导入useStore.js中以获取完整图片。


 // store/useStore.js import React, { createContext, useReducer, useContext } from "react"; import { countInitialState, countActions } from "./countActions"; import { userInitialState, userActions } from "./userActions"; //    (initial states) const initialState = { ...countInitialState, ...userInitialState }; const StoreContext = createContext(initialState); //  actions const Actions = { ...userActions, ...countActions }; const reducer = (state, action) => { const act = Actions[action.type]; const update = act(state); return { ...state, ...update }; }; export const StoreProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreContext.Provider value={{ state, dispatch }}> {children} </StoreContext.Provider> ); }; export const useStore = store => { const { state, dispatch } = useContext(StoreContext); return { state, dispatch }; }; 

我们做到了! 圈出一个荣誉圈,当您返回时,我们将看到如何在组件中使用它。


欢迎回来! 希望您的圈子真的很荣幸。 让我们看一下useStore的实际应用。


首先,我们可以将App组件包装在<StoreProvider />中


 // App.js import React from "react"; import ReactDOM from "react-dom"; import { StoreProvider } from "./store/useStore"; import App from "./App"; function Main() { return ( <StoreProvider> <App /> </StoreProvider> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<Main />, rootElement); 

我们将App包装在StoreProvider中,以便子组件可以访问提供者的值。 此值既是state也是dispatch


现在,假设我们有一个具有登录/注销按钮的AppHeader组件。


 // AppHeader.jsx import React, {useCallback} from "react"; import { useStore } from "./store/useStore"; const AppHeader = props => { const { state, dispatch } = useStore(); const login = useCallback(() => dispatch({ type: "login" }), [dispatch]); const logout = useCallback(() => dispatch({ type: "logout" }), [dispatch]); const handleClick = () => { loggedIn ? logout() : login(); } return ( <div> <button onClick={handleClick}> {loggedIn ? "Logout" : "Login"}</button> <span>{state.user.loggedIn ? "logged in" : "logged out"}</span> <span>Counter: {state.count}</span> </div> ); }; export default AppHeader; 

链接到代码沙箱并完整实施


原作者: 拉姆齐
链接到原始

Source: https://habr.com/ru/post/zh-CN460083/


All Articles