前言
react hooks 是 React 16.8 的新增特性。 它可以让我们在函数组件中使用 state 、生命周期以及其他 react特性,而不仅限于 class 组件。react hooks 的出现,标示着 react中不会在存在无状态组件了,只有类组件和函数组件。具体可查看官网。
优势:
- 函数组件不能使用state,遇到交互更改状态等复杂逻辑时不能更好地支持,hooks让函数组件更靠近class组件,拥抱函数式编程。
- 解决副作⽤问题,hooks出现可以处理数据获取、订阅、定时执行任务、手动修改 ReactDOM这些⾏为副作用,进行副作用逻辑。比如useEffect。
- 更好写出有状态的逻辑重用组件。
- 让复杂逻辑简单化,比如状态管理:useReducer、useContext。
- 函数式组件比class组件简洁,开发的体验更好,效率更⾼,性能更好。
- 更容易发现无用的状态和函数。
useState介绍
const [state, setState] = useState(initialState);
返回一个 state,以及更新 state 的函数setState。
- state是要设置的状态 。
- setState是更新state的方法。setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。
- initState是初始的state,可以是随意的数据类型,也可以是回调函数,但是函数必须是有返回值(惰性state)。
惰性初始 state initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。如果初始 state需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
}); ```
在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。
setState(newState);
这里的值不会立即变化
使用useState钩子提供的更新程序的状态更新也是异步的,并且不会立即反映和更新,但会触发重新呈现。在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state。
useState原理
我们需要写一个 useState 方法,会返回当前状态和设置状态的方法,每当状态改变之后,方法中会调用刷新视图的 render 方法。状态我们需要放在最外面,方便下次执行函数时可以重新取值。初始状态只会在函数第一次执行的时候赋值。
let memoizedState;
function useState (initialState) {
memoizedState = memoizedState || initialState
function setState (newState) {
memoizedState = newState
render()
}
return [memoizedState, setState]
}
这样确实是可以正常使用,但是当多个 useState 存在的时候就有问题了,只能变一个状态了。现在我们需要优化我们的 Hooks ,解决多个 useState 同时使用的问题,当多个状态存在的时候,我们需要使用数组保存状态。
let memoizedStates = []
let index = 0
function useState (initialState) {
memoizedStates[index] = memoizedStates[index] || initialState
let currentIndex = index
function setState (newState) {
memoizedStates[currentIndex] = newState
render()
}
return [memoizedStates[index++], setState]
}
useState使用
例子:
function App () {
const [ count, setCount ] = useState(0)
return (
<div>
点击次数: { count }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
相同值,当我们在使用 useState 时,修改值时传入同样的值,我们的组件是不会渲染的。
function App () {
const [ count, setCount ] = useState(0)
console.log('我就看你渲染不')
return (
<div>
点击次数: { count }
<button onClick={() => { setCount(count)}}>点我</button>
</div>
)
}
useState注意点
- useState最好写到函数的起始位置, 主要是便于阅读
- useState严禁出现在代码块(判断和循环等)中
- useState返回的函数(数组的第二项), 这个函数的引用是不会变化的(优化性能)
- 如果使用函数改变数据, 若数据和之前的数据完全相等(使用Object.is), 则不会重新渲染, 由于Object.is是浅比较, 所以如果状态是一个对象的时候要小心操作了
- 如果使用函数改变数据, 传入的值不会和原来的数据进行合并而是直接进行替换(跟setState完全不一样), 所以在修改对象的时候, 我们要先将之前的对象保存下来
- 不要直接去改变state的值
- 果要实现强制刷新组件的情况: 如果是类组件我们都会使用forceUpdate, 在函数组件中, 我们可以用useState来实现, 使用useState的改变state的函数传入一个空对象, 因为每次传入一个空对象的地址不一样所以一定会刷新。使用const [, forceUpdate] = useState({});forceUpdate({})
- 和类组件一样, 函数组件的状态更改在某些时候是一步的(dom事件下), 如果是异步的更改, 则多个状态的更改会合并, 此时不能信任之前的状态, 而应该使用回调函数的方式改变状态
- 如果某些状态之间没有必然的联系, 应该分化为不同的状态而非合并成一个对象。
useState更新延迟问题
看过上面的解释,相信大家知道这里要说了什么了
const [current, setCurrent] = useState(0)
const click = () => {
setCurrent(current+ 1)
console.log(current) //0
}
解决方案:
- 用useRef代替(类似沙箱,它还可以“跨渲染周期”保存数据)
- 直接用变量存储或用函数包裹(组件渲染重新刷新变量)
- 配合useEffect处理
具体情况具体处理
const current = useRef(0);
const click = () => {
current.current += 1
console.log(current.current) //1
}
const [name, setName] = useState('1');
const handleTest = () => {
console.log(name) //1
setName('2')
console.log(name) //1
}
useEffect(() => {
console.log(name) //2
},[name])