React Hooks:使用 useContext 和 useReducer 提升/传递状态
这篇文章发表在我的博客上。
我遇到过这样的情况:很多子组件和兄弟组件需要共享状态。之前,我通过prop一个方法来传递更新后的状态。但后来,props 的数量不断增加,这让我很头疼。
后来出现了一种context基于全局存储状态并跨组件共享的方法。但即使有了contextAPI,你仍然需要通过 APIrender props来使用全局存储的状态context。你很快就会意识到,你的组件会变得嵌套臃肿、难以维护,而且回头看时会让人感到厌烦。
本文将探讨如何利用 React 的最新hooks概念,以更简洁的代码实现相同的功能。
我们先来构建一个包含一些子组件和同级组件的示例用户界面。
让我们来设计用户界面
前往代码沙箱快速进行实验。请务必先创建一个React代码沙箱。
index.js请将以下内容替换为:
import React from "react";
import ReactDOM from "react-dom";
function App() {
return (
<div className="App">
<h1>Lift up / Pass down state</h1>
<UserList />
<AddGenderToUser />
<AddAgeToUser />
</div>
);
}
function UserList() {
return (
<ul>
<li>
<span>Vimalraj Selvam</span>
<button type="button">Edit</button>
</li>
<li>
<span>Bhuvaneswari Vimalraj</span>
<button type="button">Edit</button>
</li>
</ul>
);
}
function AddGenderToUser({ username }) {
return (
<div>
<h2>Add gender to {username}</h2>
<button type="button">Add Age</button>
</div>
);
}
function AddAgeToUser({ username }) {
return (
<div>
<h2>Add Age to {username}</h2>
<button type="button">Submit</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
这里我有 3 个子组件,它们都属于父App组件:UserList,AddGenderToUser和AddAgeToUser。
这是一个非常简单的例子,所以不用过多考虑这个应用程序的具体应用场景。
AddGenderToUser我希望仅在点击特定用户的按钮时才显示该组件Edit,并使用所选用户名更新组件的标题。
同样的情况也适用于AddAgeToUser组件,点击组件Add Age中的按钮时也会发生这种情况AddGenderToUser。
首先,让我们创建应用程序在未选择任何用户时的初始状态。
const initialState = {
username: null,
gender: null,
age: null
};
然后创建我们的 reducer 方法来执行不同的操作。我能想到的操作有:
- 更新用户
- 设置当前用户的性别
- 设置当前用户的年龄
我们把它放到一个reducer函数里:
const UPDATE_USER = "UPDATE_USER";
const SET_GENDER = "SET_GENDER";
const SET_AGE = "SET_AGE";
function reducer(state, action) {
switch (action.type) {
case UPDATE_USER:
return {
username: action.username,
gender: null,
age: null
};
case SET_GENDER:
return {
username: state.username,
gender: action.gender,
age: null
};
case SET_AGE:
return {
username: state.username,
gender: state.gender,
age: action.age
};
default:
return initialState;
}
}
我们的 reducer 方法非常简单。它从action参数中获取值,并将其设置为当前状态。
现在让我们在父App组件中使用useReducerReact 的 hook 来使用这个 reducer 函数。这样我们就可以通过它来使用 reducer 的属性context。
return让我们在组件语句之前添加以下这行代码App。
const [user, dispatch] = React.useReducer(reducer, initialState);
这里user是当前状态,也是dispatch我们触发 reducer 上定义的各种操作的方法。为此,我们必须将该dispatch方法向下传递,并且如果对象发生任何更新state,父对象/父对象的其他子对象也应该知晓。
为了实现上述目标,我们需要利用contextReact 的 API 来存储我们的状态并进行分发。
让我们context用以下代码行进行初始化。这行代码应该放在你的App函数之前(其实位置并不重要)。
const MyContext = React.createContext(null);
我已经将上下文初始化为 null。我们需要将状态和分发放入上下文中。为此,让我们修改App组件,将所有子组件都包裹在context'sprovider 中。更新后的App组件应该如下所示:
<MyContext.Provider value={{ user, dispatch }}>
<UserList />
{user.username && <AddGenderToUser />}
{user.gender && <AddAgeToUser />}
</MyContext.Provider>
太好了,现在我们可以访问user状态以及后续对应的方法了dispatch。另外,我还根据user状态属性添加了一些子元素的条件渲染username。gender
让我们更新组件,使其在特定用户点击按钮时UserList触发相应操作。为此,我们需要从React 的using hook 中获取该方法。UPDATE_USEREditdispatchcontextuseContext
重写后的UserList组件:
function UserList() {
const { dispatch } = useContext(MyContext);
return (
<ul>
<li>
<span>Vimalraj Selvam</span>
<button
type="button"
onClick={() => dispatch({ type: UPDATE_USER, username: "Vimalraj" })}
>
Edit
</button>
</li>
{/* Removed for brevity */}
</ul>
);
}
我们正在分发UPDATE_USERaction 并发送数据username来更新状态的属性。现在,当您点击Edit特定用户的按钮时,可以看到AddGenderToUser组件出现。但是,我们仍然无法在出现的组件中看到用户名。让我们来解决这个问题!
function AddGenderToUser() {
const { user, dispatch } = useContext(MyContext);
return (
<div>
<h2>Add gender to {user.username}</h2>
<button
type="button"
onClick={() => dispatch({ type: SET_GENDER, gender: "??" })}
>
Add Age
</button>
</div>
);
}
我们正在获取当前user状态和dispatch方法。我们提取username属性以显示在标题中,并SET_GENDER在点击Add Age按钮时触发操作。
AddAgeToUser你也可以对函数重复相同的操作。
完整版本已在代码沙箱中提供,欢迎在此查看。
在代码沙箱中,我对App组件进行了一些更新,以便在年龄更新后显示详细信息。
如果这篇文章对您有帮助,请点赞并分享。
文章来源:https://dev.to/email2vimalraj/react-hooks-lift-up--pass-down-state-using-usecontext-and-usereducer-5ai0