- 简单的useState - Counter 计数器示例
- useState with Previous State
- useState with Object
- useState with Array
- useState 总结
本系列将讲述 React Hooks 的使用方法,从 useState 开始,将包含如下内容:
掌握 React Hooks api 将更好的帮助你在工作中使用,对 React 的掌握更上一层楼。本系列将使用大量实例代码和效果展示,非常易于初学者和复习使用。
前提,需要会使用 React Class 的写法,会使用 setState()
和 props。
下面从第一个例子开始吧。
简单的useState - Counter 计数器示例
点击+1的计数器
Class 组件的写法
CounterClass.tsx
import React, { Component } from 'react'
class CounterClass extends Component {
state = {
count: 0
}
incrementCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
const { count } = this.state
return (
<div>
<button onClick={this.incrementCount}>Count {count}</button>
</div>
)
}
}
export default CounterClass
App.tsx
import React from 'react'
import './App.css'
import CounterClass from './components/CounterClass'
const App = () => {
return (
<div className="App">
<CounterClass />
</div>
)
}
export default App
效果如下
创建这样的一个计数器分为简单的3步
- 创建一个 Class 组件
- 创建 state
- 创建 increment 方法
接下来看看如何使用 Function Component 和 state hook 实现
State Hook 实现
HookCounter.tsx
import React, { useState } from 'react'
function HookCounter() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => {
setCount(count + 1)
}}>Count {count}</button>
</div>
)
}
export default HookCounter
App.tsx
import React from 'react'
import './App.css'
// import CounterClass from './components/1CounterClass'
import HookCounter from './components/1HookCounter'
const App = () => {
return (
<div className="App">
<HookCounter />
</div>
)
}
export default App
效果与 Class 组件写法相同
我们来看 useState 的使用方法
const [state, setState] = useState(initialState)
这行代码返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
小结 Hooks 的使用规则
- 只能在顶层作用域调用 Hooks
- 不能在内部的循环、条件判断、嵌套的方法中使用
- 只能在 React Function 里使用 Hooks
- 不能在其他普通的 function 中使用 Hooks
useState with Previous State
本节讲述如何使用 previous state,当你的 state 值依赖上一个状态值时,就会用到 previous state。
依然通过 Counter 的例子说明,增加了按钮点击 +1 或 -1
Counter 示例
HookCounter.tsx
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
return (
<div>
Count: {count}
<button onClick={() => {
setCount(initialCount)
}}>Reset</button>
<button onClick={() => {
setCount(count + 1)
}}> + 1 </button>
<button onClick={() => {
setCount(count - 1)
}}> - 1 </button>
</div>
)
}
export default HookCounter
App.tsx
import React from 'react'
import './App.css'
import HookCounter from './components/3HookCounter'
const App = () => {
return (
<div className="App">
<HookCounter />
</div>
)
}
export default App
效果如下:
看起来效果没有问题,但是这样写是不安全的! 这不是更改计数器的正确方法。下面告诉你为什么:
同样也是举个例子,在上述的示例中再增加一个按钮,每次累加5
代码如下
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
+ const increment5 = () => {
+ for (let i = 0; i < 5; i++) {
+ setCount(count + 1)
+ }
+ }
return (
<div>
Count: {count}
<button onClick={() => {
setCount(initialCount)
}}>Reset</button>
<button onClick={() => {
setCount(count + 1)
}}> + 1 </button>
<button onClick={() => {
setCount(count - 1)
}}> - 1 </button>
+ <button onClick={increment5}> + 5 </button>
</div>
)
}
export default HookCounter
点击 + 5 时,只加了 1
这是因为 setCount 方法是异步的,不能立即反应并更新,瞬间调动多次入参中的 count 仍然是旧的值,没有被更新。
将代码修改如下:
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const increment5 = () => {
for (let i = 0; i < 5; i++) {
+ setCount(prevCount => prevCount + 1)
}
}
return (
<div>
Count: {count}
<button onClick={() => {
setCount(initialCount)
}}>Reset</button>
<button onClick={() => {
setCount(count + 1)
}}> + 1 </button>
<button onClick={() => {
setCount(count - 1)
}}> - 1 </button>
<button onClick={increment5}> + 5 </button>
</div>
)
}
export default HookCounter
注意到如果要使用 previous state,则需要通过 function 的方式传入 value 再返回变化后的新 value,将 + 1
-1
的功能也修改一下,完善后的代码如下:
import React, { useState } from 'react'
function HookCounter() {
const initialCount = 0
const [count, setCount] = useState(initialCount)
const increment5 = () => {
for (let i = 0; i < 5; i++) {
setCount(prevCount => prevCount + 1)
}
}
return (
<div>
Count: {count}
<button onClick={() => {
setCount(initialCount)
}}>Reset</button>
<button onClick={() => {
setCount(prevCount => prevCount + 1)
}}> + 1 </button>
<button onClick={() => {
setCount(prevCount => prevCount - 1)
}}> - 1 </button>
<button onClick={increment5}> + 5 </button>
</div>
)
}
export default HookCounter
小结
使用 previousState 时,要使用 setter function 的方式,传参给 setState 方法。来确保拿到的是准确的 previous state。
在重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
useState with Object
当 useState 中的 state 为对象时,调用相应的 setState 有一些要注意的地方,useState 不会自动合并更新对象,你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
错误示例 firstName & lastName
HookCounter.tsx
import React, { useState } from 'react'
function HookCounter() {
const [name, setName] = useState({
firstName: '',
lastName: ''
})
return (
<form>
<input
type="text"
value={name.firstName}
onChange={e => {
setName({
firstName: e.target.value
})
}}
/>
<input
type="text"
value={name.lastName}
onChange={e => {
setName({
lastName: e.target.value
})
}}
/>
<h2>Your first name is {name.firstName}</h2>
<h2>Your last name is {name.lastName}</h2>
</form>
)
}
export default HookCounter
注意到 input 标签上的 onChange 中,每次 setName 时,只对对象中的一个属性做了操作。只给 firstName 赋值时,lastName 属性会消失,这是一种错误的写法。
由于我使用了 tsx 来写组件,我的编译器直接就报错了:
浏览器也直接报错了
正确示例-手动合并对象
这里要使用展开运算符,来解决这个对象的问题
import React, { useState } from 'react'
function HookCounter() {
const [name, setName] = useState({
firstName: '',
lastName: ''
})
return (
<form>
<input
type="text"
value={name.firstName}
onChange={e => {
setName({
...name,
firstName: e.target.value
})
}}
/>
<input
type="text"
value={name.lastName}
onChange={e => {
setName({
...name,
lastName: e.target.value
})
}}
/>
<h2>Your first name is {name.firstName}</h2>
<h2>Your last name is {name.lastName}</h2>
<h2>{JSON.stringify(name)}</h2>
</form>
)
}
export default HookCounter
小结
state hook 中操作对象时,不会自动合并对象中的属性,需要我们手动来合并,可以运用展开运算符。
那么,数组也是类似的,见下一节。
useState with Array
列表示例
点击按钮,列表增加一个1-10的随机数
UseStateWithArray.tsx
import React, { useState } from 'react'
interface ItemType {
id: number
value: number
}
function UseStateWithArray() {
const [items, setItems] = useState<ItemType[]>([])
const addItem = () => {
setItems([
...items,
{
id: items.length,
value: Math.ceil(Math.random() * 10)
}
])
}
return (
<div>
<button onClick={addItem}>add a number</button>
<ul>
{
items.length > 0 && items.map((item: ItemType) => (
<li key={item.id}>{item.value}</li>
))
}
</ul>
</div>
)
}
export default UseStateWithArray
效果如下:
注意 typeScript 在 hooks 中的使用方法,可参考如下文章
useState 总结
关于 useState 的使用就到这里,这里稍作总结。
- 可以在函数式组件中使用 state
- 在类组件中,state 是一个对象,但是 useState 中,state可以不是对象,可以是任何类型
- useState 返回2个元素的数组
- 第一个是 state 的当前值
- 第二个是 state 的 setter 方法,调用时会触发 rerender
- 若当前的 state 依赖 previous state,可以传入一个函数到 state 的 setter 方法中,入参为 previous state,返回新的 state
- 对于对象和数组,注意 state 中不会自动补全旧的变量,需要使用展开运算符自己手动补充
useState 的学习就到这里,下一篇将一起来学习 useEffect 的使用。