我们使用 状态 来跟踪应用程序数据。 状态会随着用户与应用程序的交互而改变。 发生这种情况时,我们需要更新显示给用户的状态,我们使用 React 的 setState 来实现。
由于状态不应直接更新(因为 React 的状态必须是不可变的),因此随着状态变得更加复杂,事情会变得非常复杂。 它们变得难以理解和跟踪。
这就是 Immer 出现的地方,这也是我们将在本文中探讨的内容。 使用 Immer,状态可以简化并更容易跟踪。 Immer 使用了一种称为“草稿”的东西,您可以将其视为您的状态副本,而不是状态本身。 就像 Immer 在状态上按下了 CMD+C 然后在某个安全的地方按下了 cmd+V,这样就可以安全地查看而不会干扰原始副本。 您需要进行的任何更新都会发生在草稿上,并且当前状态中草稿上发生更改的部分会得到更新。
假设您的应用程序状态如下;
this.state = {
name: 'Kunle',
age: 30,
city: 'Lagos,
country: 'Nigeria'
}
这位用户碰巧正在庆祝他的 31 岁生日,这意味着我们需要更新年龄值。 在 Immer 在后台运行的情况下,将创建此状态的副本。
现在想象一下,副本被制作出来并交给了信使,信使将状态的新副本交给 Kunle。 这意味着现在有两个副本可用 - 当前状态和交给 Kunle 的草稿副本。 然后,Kunle 将草稿上的年龄更改为 31。 然后信使带着草稿回到应用程序,比较两个版本,只更新年龄,因为这是草稿中唯一更改的部分。
它并没有破坏不可变状态的概念,因为当前状态不会直接更新。 Immer 基本上使使用不可变状态变得很方便。
让我们看一个实际的例子
假设您想为您的社区建造一个交通灯,您可以尝试使用 Immer 来更新您的状态。
查看 CodePen 上的
使用 Reactjs 的交通灯示例,由 CarterTsai (@CarterTsai) 创建。
在 CodePen 上。
使用 Immer,该组件将如下所示
const {produce} = immer
class App extends React.Component {
state = {
red: 'red',
yellow: 'black',
green: 'black',
next: "yellow"
}
componentDidMount() {
this.interval = setInterval(() => this.changeHandle(), 3000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
handleRedLight = () => {
this.setState(
produce(draft => {
draft.red = 'red';
draft.yellow = 'black';
draft.green = 'black';
draft.next = 'yellow'
})
)
}
handleYellowLight = () => {
this.setState(
produce(draft => {
draft.red = 'black';
draft.yellow = 'yellow';
draft.green = 'black';
draft.next = 'green'
})
)
}
handleGreenLight = () => {
this.setState(
produce(draft => {
draft.red = 'black';
draft.yellow = 'black';
draft.green = 'green';
draft.next = 'red'
})
)
}
changeHandle = () => {
if (this.state.next === 'yellow') {
this.handleYellowLight()
} else if (this.state.next === 'green') {
this.handleGreenLight()
} else {
this.handleRedLight()
}
}
render() {
return (
<div className="box">
<div className="circle" style={{backgroundColor: this.state.red}}></div>
<div className="circle" style={{backgroundColor: this.state.yellow}}></div>
<div className="circle" style={{backgroundColor: this.state.green}}></div>
</div>
);
}
};
produce
是我们从 Immer 中获得的默认函数。 在这里,我们将其作为值传递给 setState()
方法。 produce
函数接受一个函数,该函数接受 draft
作为参数。 正是在这个函数中,我们可以设置草稿副本,我们希望用它来更新我们的状态。
如果看起来很复杂,还有另一种方法可以写这个。 首先,我们创建一个函数。
const handleLight = (state) => {
return produce(state, (draft) => {
draft.red = 'black';
draft.yellow = 'black';
draft.green = 'green';
draft.next = 'red'
});
}
我们将应用程序的当前状态和接受 draft
作为参数的函数传递给 produce
函数。 为了在我们的组件中使用它,我们执行以下操作;
handleGreenLight = () => {
const nextState = handleLight(this.state)
this.setState(nextState)
}
另一个例子:购物清单
如果您已经使用 React 一段时间了,那么您并不陌生于 扩展运算符。 使用 Immer,您无需使用扩展运算符,尤其是在处理状态中的数组时。
让我们通过创建一个购物清单应用程序来进一步探索它。
查看 CodePen 上的
immer 2 - 购物清单,由 Kingsley Silas Chijioke (@kinsomicrote) 创建。
在 CodePen 上。
这是我们正在使用的组件
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
item: "",
price: 0,
list: [
{ id: 1, name: "Cereals", price: 12 },
{ id: 2, name: "Rice", price: 10 }
]
}
}
handleInputChange = e => {
this.setState(
produce(draft => {
draft[event.target.name] = event.target.value
}))
}
handleSubmit = (e) => {
e.preventDefault()
const newItem = {
id: uuid.v4(),
name: this.state.name,
price: this.state.price
}
this.setState(
produce(draft => {
draft.list = draft.list.concat(newItem)
})
)
};
render() {
return (
<React.Fragment>
<section className="section">
<div className="box">
<form onSubmit={this.handleSubmit}>
<h2>Create your shopping list</h2>
<div>
<input
type="text"
placeholder="Item's Name"
onChange={this.handleInputChange}
name="name"
className="input"
/>
</div>
<div>
<input
type="number"
placeholder="Item's Price"
onChange={this.handleInputChange}
name="price"
className="input"
/>
</div>
<button className="button is-grey">Submit</button>
</form>
</div>
<div className="box">
{
this.state.list.length ? (
this.state.list.map(item => (
<ul>
<li key={item.id}>
<p>{item.name}</p>
<p>${item.price}</p>
</li>
<hr />
</ul>
))
) : <p>Your list is empty</p>
}
</div>
</section>
</React.Fragment>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
随着项目被添加到清单中,我们需要更新清单的状态以反映这些新项目。 要使用 setState()
更新 list
的状态,我们必须这样做
handleSubmit = (e) => {
e.preventDefault()
const newItem = {
id: uuid.v4(),
name: this.state.name,
price: this.state.price
}
this.setState({ list: [...this.state.list, newItem] })
};
如果您必须更新应用程序中的多个状态,则必须进行大量的扩展以使用旧状态和附加值创建新状态。 随着更改次数的增加,这看起来可能会更加复杂。 使用 Immer,这变得非常容易,就像我们在上面的示例中所做的那样。
如果我们想添加一个函数,该函数在状态更新之后被调用作为回调? 在这种情况下,假设我们正在跟踪清单中项目的数量和所有项目的总价格。
查看 CodePen 上的
immer 3 - 购物清单,由 Kingsley Silas Chijioke (@kinsomicrote) 创建。
在 CodePen 上。
假设我们想根据清单中项目的價格计算总支出,我们可以让 handleSubmit
函数如下所示
handleSubmit = (e) => {
e.preventDefault()
const newItem = {
id: uuid.v4(),
name: this.state.name,
price: this.state.price
}
this.setState(
produce(draft => {
draft.list = draft.list.concat(newItem)
}), () => {
this.calculateAmount(this.state.list)
}
)
};
首先,我们使用用户输入的数据创建一个对象,然后将其分配给 newItem
。 为了更新我们应用程序的状态,我们使用 .concat()
,它将返回一个由之前项目和新项目组成的新的数组。 此更新的副本现在被设置为 draft.list
的值,然后 Immer 可以使用它来更新应用程序的状态。
回调函数在状态更新后被调用。 重要的是要注意它使用的是更新后的状态。
我们想要调用的函数将如下所示
calculateAmount = (list) => {
let total = 0;
for (let i = 0; i < list.length; i++) {
total += parseInt(list[i].price, 10)
}
this.setState(
produce(draft => {
draft.totalAmount = total
})
)
}
让我们看看 Immer 挂钩
use-immer
是一个挂钩,允许您在 React 应用程序中管理状态。 让我们使用经典的计数器示例来看看它的实际作用。
import React from "react";
import {useImmer} from "use-immer";
const Counter = () => {
const [count, updateCounter] = useImmer({
value: 0
});
function increment() {
updateCounter(draft => {
draft.value = draft.value +1;
});
}
return (
<div>
<h1>
Counter {count.value}
</h1>
<br />
<button onClick={increment}>Increment</button>
</div>
);
}
export default Counter;
useImmer
类似于 useState。 该函数返回状态和一个更新器函数。 当组件首次加载时,状态的值(在本例中为 count
)与传递给 useImmer
的值相同。 使用返回的更新器函数,我们可以创建一个 increment
函数来增加计数的值。
还有一个 useReducer
样式的 Immer 挂钩。
import React, { useRef } from "react";
import {useImmerReducer } from "use-immer";
import uuidv4 from "uuid/v4"
const initialState = [];
const reducer = (draft, action) => {
switch (action.type) {
case "ADD_ITEM":
draft.push(action.item);
return;
case "CLEAR_LIST":
return initialState;
default:
return draft;
}
}
const Todo = () => {
const inputEl = useRef(null);
const [state, dispatch] = useImmerReducer(reducer, initialState);
const handleSubmit = (e) => {
e.preventDefault()
const newItem = {
id: uuidv4(),
text: inputEl.current.value
};
dispatch({ type: "ADD_ITEM", item: newItem });
inputEl.current.value = "";
inputEl.current.focus();
}
const handleClear = () => {
dispatch({ type: 'CLEAR_LIST' })
}
return (
<div className='App'>
<header className='App-header'>
<ul>
{state.map(todo => {
return <li key={todo.id}>{todo.text}</li>;
})}
</ul>
<form onSubmit={handleSubmit}>
<input type='text' ref={inputEl} />
<button
type='submit'
>
Add Todo
</button>
</form>
<button
onClick={handleClear}
>
Clear Todos
</button>
</header>
</div>
);
}
export default Todo;
useImmerReducer
接收一个 reducer 函数和初始状态,并返回状态和分派函数。 然后,我们可以遍历状态以显示我们拥有的项目。 我们在提交待办事项项目并清除其列表时分派一个操作。 分派的 action 具有一个类型,我们使用它来确定在 reducer 函数中要做什么。
在 reducer 函数中,我们像以前一样使用 draft
,而不是 state
。 通过这样做,我们有一个方便的方法来操作我们应用程序的状态。
您可以在 GitHub 上找到上述示例中使用的代码。
这就是 Immer 的概览!
展望未来,您可以在下一个项目中开始使用 Immer,甚至可以逐步开始在您正在进行的当前项目中使用它。 它已被证明有助于使状态管理变得方便。
不错的文章,非常有趣的软件包