Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
系列目录
- React新特性-Context(一)
- React新特性-Lazy和Suspense(二)
- React新特性-memo(三)
- React新特性-Hooks之useState(四)
- React新特性-Hooks之useEffect和useLayoutEffect(五)
- React新特性-Hooks之useContext(六)
- React新特性-Hooks之useRef(七)
- React新特性-Hooks之useMemo和useCallback(八)
- React新特性-Hooks之useReducer(九)
- React新特性-Hooks之自定义Hook(十完结)
为什么要使用Hook
Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。
在组件之间复用状态逻辑很难
- 缺少复用机制
- 渲染属性和高阶组件导致层级冗余
复杂组件变得难以理解
- 生命周期函数混杂不相干逻辑
- 相干逻辑分散在不同生命周期
难以理解的 class
- this 指向困扰
- 内联函数过度创建新句柄
使用Hook的优势
- 函数组件无 this 问题(拥抱函数式编程)
- 自定义 Hook 方便复用状态逻辑
- 副作用的关注点分离
使用useState
你可以把
useState
理解为以前class组件的this.setState
。useState
是允许你在 React 函数组件中添加 state 的 Hook。
来对比一下使用class和Hook的区别
import React, { useState } from 'react'
//未使用React Hook class组件
export default class TestInput extends React.Component {
//初始化state
state = {
inputValue: '默认值'
}
render() {
const { inputValue } = this.state;
return (
<div>
<input value={inputValue} onChange={(e) => this.setState({ inputValue: e.target.value })} /> {inputValue}
</div>
)
}
}
//使用React Hook 函数组件
const TestInput= () => {
//声明和初始化state
const [inputValue,setInputValue] = useState('默认值')
return (
<div>
<input value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> {inputValue}
</div>
)
}
export default TestInput
它定义一个 “state”的变量。这里useState
接收了一个默认值,useState
返回的是一个数组。
数组里面总共是两个元素[“默认值”,匿名函数],这个匿名函数就相遇于class组件中的this.setState()
,只不过匿名函数中是改变单个变量的值。
useState
返回了两个元素,我们使用了es6中的数据解构的方式去赋值给inputValue
和setInputValue
。
当调用了setInputValue
之后,整个TestInput
会重新渲染,在后续的重新渲染中,useState
返回的第一个值将始终是更新后最新的inputValue
的值。
useState使用技巧
声明多个 state 变量
function TestInput() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
我们可以在单个组件中使用多个 State Hook ?答案是 React 靠的是 Hook 调用的顺序。因为我们的示例中,Hook 的调用顺序在每次渲染中都是相同的,所以它能够正常工作
function TestInput() {
// ------------
// 首次渲染
// ------------
const [age, setAge] = useState(42); // 1. 使用 42 初始化变量名为 age 的 state
const [fruit, setFruit] = useState('banana'); // 2. 使用 banana 初始化变量名为 fruit 的 state
// -------------
// 二次渲染
// -------------
const [age, setAge] = useState(42); // 1. 读取变量名为 age 的 state(参数被忽略)
const [fruit, setFruit] = useState('banana'); // 2. 读取变量名为 fruit 的 state(参数被忽略)
}
只要 Hook 的调用顺序在多次渲染之间保持一致,React 就能正确地将内部 state 和对应的 Hook 进行关联
注意:不要在循环、条件判断或者子函数中调用useState。只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
如下代码:
let id = 0
function App () {
let name,setName;
let count,setCount;
id += 1;
if (id & 1) {
// 奇数
[count, setCount] = useState(0)
[name, setName] = useState('张三')
} else {
// 偶数
[name, setName] = useState('李四')
[count, setCount] = useState(0)
}
return (
<button type="button"
onClick={() => {setCount(count + 1) }}
>
点击({count}), 名称 ({name})
</button>
)
}
这种方式在条件语句中使用 Hook 违反第一条规则,目前这种方式React会直接给与报错提示。
函数式更新
如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
数量: {count}
<button onClick={() => setCount(initialCount)}>重置</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
</>
);
}
“+” 和 “-” 按钮采用函数式形式,因为被更新的 state 需要基于之前的 state。但是“重置”按钮则采用普通形式,因为它总是把 count 设置回初始值。
//class 组件
this.setState((prevState)=>({count:prevState.count+1}))
与 class 组件中的 setState
方法不同,useState
不会自动合并更新对象。你可以用函数式的 setState
结合展开运算符来达到合并更新对象的效果。
惰性初始 state
defaultValue
参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
//每次setInputValue引起的重新渲染进入函数组件内defaultValue都会被忽略。
const TestInput= (props) => {
const defaultValue = props.value || '请输入'
const [inputValue,setInputValue] = useState(defaultValue)
return (
<div>
<input value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> {inputValue}
</div>
)
}
export default TestInput
//使用函数来解决每次渲染 state 需要通过复杂计算获得的问题
const TestInput= (props) => {
const [inputValue,setInputValue] = useState(()=>{
return props.value || '请输入'
})
return (
<div>
<input value={inputValue} onChange={(e)=>setInputValue(e.target.value)} /> {inputValue}
</div>
)
}
export default TestInput