用于为函数组件引入状态。
通常情况下,函数中的变量在函数执行完就会被释放掉,useState
方法通过闭包(将数据存储在组件函数外)让函数型组件实现保存和变更状态。
useState
接收一个初始状态的值作为参数,返回一个数组,数组的第一个元素是状态数据,第二个元素是设置状态数据的方法,该方法接收一个参数用于修改状态数据。
可以通过数组解构的方式将数组中的元素解构出来。
组件重新渲染时,useState
会获取状态的值,忽略设置的初始值。
import {
useState } from 'react'
function App() {
const [count, setCount] = useState(0)
return <div>
<span>{
count}</span>
<button onClick={
() => setCount(count+1)}>+ 1</button>
</div>
}
export default App
set
开头,后面加上状态名称// 参数是函数的场景,优先取组件接收的数据,这样只会在挂载时获取一次 props 中的数据
function App(props) {
// 错误写法:每次渲染都会获取 props.count
// const propCount = props.count
// const [count, setCount] = useState(propCount || 0)
// 正确写法:只会在挂载时执行一次
const [count, setCount] = useState(() => {
return props.count || 0
})
return <div>
<span>{
count}</span>
<button onClick={
() => setCount(count+1)}>+ 1</button>
</div>
}
setState
一样合并对象类型的状态值。import {
useState } from 'react'
function App(props) {
const [count, setCount] = useState(() => {
return props.count || 0
})
function handlePower(number) {
setCount(() => {
const power = number * number
// 同步代码在 setCount 参数内定义
// document.title = count
return power
})
// count 为 setCount 执行前的值
document.title = count
}
return <div>
<span>{
count}</span>
<button onClick={
() => setCount(count+1)}>+ 1</button>
<button onClick={
() => handlePower(count)}>求平方</button>
</div>
}
export default App
useState
的替代方案,是另一种让函数组件引入状态的方式。
useReducer
的方式类似 Redux,组件的状态被保存在特定的地方,要想改变状态,需要通过 dispatch
方法触发一个 Action,这个 Action 会被 Reducer 函数接收,Reducer 内部要根据 Action 的类型决定如何处理状态,最后通过返回值的方式更新状态。
useReducer
方法的参数:
(state, action) => newState
的 Reducer 函数返回:当前的 state 和配套的 dispatch 方法。
import {
useReducer } from 'react'
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
default:
return state
}
}
function App() {
const [count, dispatch ] = useReducer(reducer, 0)
return <div>
<span>{
count}</span>
<button onClick={
() => dispatch({
type: 'increment'})}>+ 1</button>
<button onClick={
() => dispatch({
type: 'decrement'})}>- 1</button>
</div>
}
export default App
useReducer
方便根据 Action 的类型修改部分数据。dispatch
方法,而不是组件内定义的函数dispatch
的引用是固定的,而组件内定义的函数在组件重新渲染时被重新定义,因此也会触发下级组件的渲染在使用 createContext 跨组件层级传递数据时,简化获取数据的代码。
useContext
接收一个 context 对象(React.createContext 的返回值),返回该 context 的当前值。
调用了 useContext
的组件总会在 context 值变化时重新渲染。
import React, {
createContext } from 'react'
const ThemeContext = createContext()
class Foo extends React.Component {
static contextType = ThemeContext
render() {
return <div>{
this.context }</div>
}
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Foo />
</ThemeContext.Provider>
)
}
export default App
import {
createContext } from 'react'
const ThemeContext = createContext()
function Foo() {
return <ThemeContext.Consumer>
{
theme => <div>{
theme }</div>
}
</ThemeContext.Consumer>
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Foo />
</ThemeContext.Provider>
)
}
export default App
使函数型组件中不适用组件嵌套(Consumer)就可以订阅 Context。
import {
createContext, useContext } from 'react'
const ThemeContext = createContext()
function Foo() {
const theme = useContext(ThemeContext)
return <div>{
theme }</div>
}
function App() {
return (
<ThemeContext.Provider value="dark">
<Foo />
</ThemeContext.Provider>
)
}
export default App
让函数型组件拥有处理副作用的能力,类似生命周期函数。
useEffect
处理副作用。useEffect
可以看作 componentDidMount
、componentDidUpdate
和 componentWillUnmount
三个函数的组合:
useEffect(() => {}) ==> componetDidMount, componentDidUpdate
useEffect(() => {}, []) ==> componetDidMount
useEffect(() => () => {}) ==> componetWillUnmount
当组件重新渲染时,useEffect
会用新的副作用函数替换之前的副作用函数。
import {
useState, useEffect } from 'react'
import ReactDom from 'react-dom'
function Foo(props) {
useEffect(() => {
console.log('1. 组件挂载完成和状态更新完成时执行')
})
useEffect(() => {
console.log('2. 仅在组件挂载完成时执行一次')
}, [])
useEffect(() => {
return () => {
console.log('3. 在组件更新前和卸载前执行', 'count: '+ count)
}
})
return <div>{
props.count}</div>
}
function App(props) {
const [count, setCount] = useState(0)
return <div>
<Foo count={
count} />
<button onClick={
() => setCount(count+1)}>+ 1</button>
<button onClick={
() => ReactDom.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>
}
export default App
ReactDom.unmountComponentAtNode(container)
用于卸载容器中渲染的组件。
index.html 文件中给 body 添加高度:
<body style="height: 2000px;">
<div id="root">div>
body>
import {
useState, useEffect } from 'react'
import ReactDom from 'react-dom'
function App() {
function onScroll() {
console.log('页面滚动了')
}
useEffect(() => {
window.addEventListener('scroll', onScroll)
return () => {
window.removeEventListener('scroll', onScroll)
}
}, [])
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
// 这里需使用函数,将 count 作为参数传入,否则数值只会更新一次
setCount(count => {
document.title = count + 1
return count + 1
})
}, 1000)
return () => {
clearInterval(timerId)
}
}, [])
return (
<div>
<span>{
count}</span>
<button onClick={
() => ReactDom.unmountComponentAtNode(document.getElementById('root'))}>卸载组件</button>
</div>
);
}
export default App;
useEffect
可以被多次调用,所以将一组相干的业务逻辑归置到同一个副作用函数中,将不相干的业务逻辑分置到不同的副作用函数中componentDidMount
和 componentDidUpdate
中编写重复代码useEffect
的第二个参数是依赖项数组,作用是:只有指定数据发生变化时才会触发副作用(effect)。
原理是接收一个给定值的数组,组件每次渲染,用新的数组和旧的数组去对比,有任何一项不相等则执行副作用。
import {
useState, useEffect } from 'react'
function App() {
const [count, setCount] = useState(0)
const [person, setPerson] = useState({
name:'张三'})
useEffect(() => {
console.log('只有当 count 变化时才会执行回调函数')
document.title = count
}, [count])
return (
<div>
<span>{
count}</span>
<button onClick={
() => setCount(count + 1)}>+ 1</button>
<br/>
<span>{
person.name}</span>
<button onClick={
() => setPerson({
name: '李四'})}>更名</button>
</div>
);
}
export default App;
在 useEffect
回调函数中执行异步操作,例如:
import {
useEffect } from 'react'
function App() {
useEffect(() => {
getData().then(result => {
console.log(result)
})
}, [])
return (
<div>App</div>
);
}
// 模拟的异步操作
function getData() {
return new Promise(resolve => {
resolve({
msg: 'Hello Async'})
})
}
export default App;
如果想使用await
关键字,则需要添加 async
关键字:
useEffect(() => {
const asyncFn = async () => {
const result = await getData()
console.log(result)
}
asyncFn()
}, [])
但是这样写,会出现问题:
# 在 React 16 中会报错打断运行(Error)
An effect function must not return anything besides a function, which is used for clean-up.
It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:<推荐写法>
# 副作用函数必须返回一个用于清理的普通函数。
# 看起来你编写了 useEffect(async () => ...) 或返回了一个 Promise。相反,你可以在副作用函数中编写异步函数,并立即调用它:<推荐写法>
# 在 React 17 中会警告(Warning)
Effect callbacks are synchronous to prevent race conditions. Put the async function inside:<推荐写法>
# 为了防止竞态条件(异步创建的任务无法确定执行顺序),副作用回调函数都是同步的。将异步函数像这样写入:<推荐写法>
How to fetch data with React Hooks?
如控制台提示的,副作用函数必须返回一个用做清理资源(组件销毁时调用)的普通函数。
如果使用 async
关键字声明函数,则会声明一个异步函数,异步函数会返回一个 Pormise,副作用函数返回 Promise,违反了使用规则。
React 17 这种写法虽然不会阻塞程序,但也不建议这样使用。
如官方推荐的写法,在普通函数中执行异步操作,将普通函数传给 useEffect
。
useEffect(() => {
const asyncFn = async () => {
const result = await getData()
console.log(result)
}
asyncFn()
}, [])
或在自执行函数中执行:
useEffect(() => {
(async () => {
const result = await getData()
console.log(result)
})()
}, [])