如果你对reducer非常熟悉,你应该对React的useReducer钩子有很好的理解。简单来说,它允许React中的函数组件从你的状态管理中访问reducer函数。
useReducer钩子的基本设置如下。
const [state, dispatch] = useReducer(reducer, initialState);
Example Store
让我们来看一个使用Reducer的例子。它总是从状态管理开始--你的数据存储在远离组件的地方。这里有一个数据存储,其中我们有四个活人(这是我们的初始状态)。
const people = [
{name: 'Jay', alive: true},
{name: 'Kailee', alive: true},
{name: 'John', alive: true},
{name: 'Mia', alive: true}
]
就这样,四个活泼的人。但时间不长,因为那不好玩。让我们制造一个Reducer来消灭他们吧
const reducer = (people, action) => {
if(action.type == 'chomp') {
return people.map(person => {
if(person.name == action.payload) {
person.alive = false;
}
return person;
})
}
if(action.type == 'revive') {
return people.map(person => {
if(person.name == action.payload) {
person.alive = true;
}
return person;
})
}
}
你可以选择你的谋杀方法。"焚烧","绞死 "等等。重要的是,我们可以检测到谋杀的方法,并记录我们杀死了我们的目标。如果他们已经死了,我们也有能力让他们复活--这可能是个不错的主意,因为我们要用他们来做实验。
你会注意到在我们的reducer中,传统的action.payload。你可以把这个名字和action.type一起改成任何适合你的应用的名字。这里需要注意的是我们传递给reducer函数的内容:people(我们的初始状态)和action(你可以把它想象成一个setState方法)。action.type标识了我们要做的动作类型,action.payload为我们标识了我们要对哪个人执行动作。
作为一个补充,我们可以使用我们的reducer函数,当我们杀死一个人的时候,将其从我们的数组中完全删除。在我们的例子中,我们使用布尔函数将他们切换为死或活。无论哪种方式都可以用reducer轻松管理。
useReducer 钩子
现在我们有了reducer和store的设置,让我们用useReducer钩子来实现它们。和其他React钩子一样,我们可以从react导入useReducer。
import React, { useReducer } from 'react';
const [state, dispatch] = useReducer(reducer, people)
这里的useReducer(reducer, people)中的reducer就是我们前面定义的常量。这些名称需要匹配才能使用我们定义的reducer。和我们的初始状态为people一样,也是我们之前定义的。这两个常量的名字随便你怎么命名都可以,只要符合你的口味就可以了,state代表了我们商店里的人被传递进来,dispatch算是我们reducer中action的别名。
JSX
你可以通过Context API或任何你选择的方式将useReducer传递给你的组件。你可以在你的组件文件中加入useReducer,然后简单地导入你的reducer/initialState。对于上下文,你可以使用useContext Hook将你的常量钩入到useReducer中。
const {state, dispatch} = useContext(StoreContext);
这里有更多关于useContext的内容
一旦你把你的reducer导入到你的JSX中,你就可以实现它并设置我们的dispatch函数来渲染到UI中。
return (
<div>
{state.map((person, idx) => (
<div key={idx} style={{display: 'flex', width: '50%', justifyContent: 'space-around'}}>
<div>{person.name}</div>
{person.alive ?
<div> ALIVE!</div> :
<div> DEAD</div>}
</div>
))}
</div>
)
这里的state,我们映射过来的,是我们在useReducer中定义的状态常量,并导入过来。它代表了我们的 people 商店的当前状态,所以 state 是一个合适的名字。在我们的 "people "存储中,每个人都有一个名字和一个活着的布尔值来判断他们是否活着。
接下来,让我们实现我们的调度函数。还记得我们是如何设置动作的吗,为了更新我们的存储,我们需要的信息。我们用一个类型来标识动作,用一个有效载荷来标识谁是我们的下一个受害者。重要的是,我们调度的动作要有匹配的标识符。
function devour(name) {
dispatch({ type: 'chomp', payload: name });
}
function spitOut(name) {
dispatch({ type: 'revive', payload: name });
}
return (
<div>
{state.map((person, idx) => (
<div key={idx} style={{ display: 'flex', width: '50%', justifyContent: 'space-around' }}>
<div>{person.name}</div>
{person.alive ?
<div> ✨✨ ALIVE! ✨✨ <button onClick={() => devour(person.name)}> 🐊 DEVOUR 🐊 </button> </div> :
<div> ☠ ☠ DEAD ☠ ☠ <button onClick={() => spitOut(person.name)}> 🥵 SPIT OUT 🥵 </button> </div>}
</div>
))}
</div>
)
dispatch方法接收一个对象,这个对象代表了我们想要做的动作。我们最终通过useReducer将我们的动作传递给我们的reducer。我们的reducer会返回更新后的状态。
还有第三个参数useReducer可以接收。这是可选的init函数,它将允许你懒惰地创建初始状态。如果你希望初始状态根据不同的情况而有所不同,这很有帮助;我们可以在任何地方创建初始状态,也许是动态的,而不是使用我们上面的people常量,它将覆盖我们的初始状态。
const deadPeople = () => ([
{ name: 'Jay', alive: false },
{ name: 'Kailee', alive: false },
{ name: 'John', alive: false },
{ name: 'Mia', alive: false }
])
// ...
// wherever our useReducer is located
const [state, dispatch] = useReducer(reducer, people, deadPeople);
注意,这个新的初始状态是一个函数,而不仅仅是一个数组。第三个参数需要一个函数来覆盖初始状态。
进一步阅读
请看官方文档了解更多。从创作者那里直接阅读总是好的,特别是当人们的生命悬于一线的时候。