# React Hooks
# Hooks的优势
- 函数式组件无this问题
- 自定义Hook方便复用状态逻辑
- 副作用的关注点分离
# State Hooks
useState
# Effect Hooks
useEffect
- componentDidMount 组件挂载之后
- componentDidUpdate 组件更新之后
- componentWillUnMount 组件将要卸载之前
在useEffect
中使用:
import React , { useState , useEffect } from 'react';
const App = () => {
const [count , setCount] = useState(0);
useEffect(() => {
// 在这里面相当于执行了 compoentDidMount 和 componentDidUpDate
document.title = `count: ${count}`
})
return (
<div>
<p>i am app</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>ADD</button>
</div>
)
}
export default App;
# 通过跳过 Effect 进行性能优化
在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate
中添加对 prevProps
或 prevState
的比较逻辑解决:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
在hooks中使用
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
完整案例:
import React , { useState , useEffect } from 'react';
const App = () => {
const [count , setCount] = useState(0);
const [name , setName] = useState('hanzo');
// 第一个effect
useEffect(() => {
console.log('render count');
document.title = `count: ${count}`
},[count]) // 当count 发送变化的时候 才会执行 DidUpdate 内容
// 在第一个effect中第二个参数 添加 count 之后,出发 setName 方法 第一个effect没有执行,因为count没有发生变化,从而不会执行更新`componentDidUpdate`
// 第二个effect
useEffect(() => {
console.log('render name');
})
return (
<div>
<p>i am app</p>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>ADD</button>
<p>{name}</p>
<button onClick={() => setName('')}>setName</button>
</div>
)
}
export default App;
# useEffect 使用 componentWillUnmount (需要清除的Effect)
案例:假若我们有一个setInterval
方法来每秒钟执行一些操作,但是在页面卸载的时候需要清除掉这个定时器方法
在class组件中:
import React from 'react';
class App extends React.Component {
this.state = {
timer: null
}
// ....
componentDidUpdate() {
this.timer = setInterval(() => {
console.log('1s后重复执行')
})
},
componentWillUnmount() {
// 清除定时器任务
clearInterval(this.timer);
}
}
在hooks中:
import React , { useState , useEffect } from 'react';
export defalut const App = () => {
const [timer] = useState(null);
useEffect(() => {
timer = setInterval(() => {
console.log('1s后重复执行')
},1000)
return () => {
// 执行 componentWillUnMount
// 清除定时器任务
clearInterval(timer);
}
})
return (
// .....
)
}
# Context Hooks
useContext
的用处和 Context.Consumer
效果一样,不过 useContext
用于函数式组件,Context.Consumer
则用于类组件
# 使用
import React , { useState , useContext , createContext } from 'react';
const CountContext = createContext();
function Counter() {
const count = useContext(CountContext)
return (
<h1>Counter == {count}</h1>
)
}
function App() {
const [count,setCount] = useState(0)
return (
<div>
<p>{count}</p>
<button onClick={() => {setCount(count + 1)}}>Add</button>
<CountContext.Provider value={count}>
<Counter />
</CountContext.Provider>
</div>
)
}
export default App;
# Memo Hooks
在hooks出来之前,一般组件想拥有状态,都是创建class组件的。
同时,在react中,性能优化点在于:
- 调用setState,就会触发组件的重新渲染,无论前后的state是否不同。
- 父组件更新,子组件也会自动的更新。
基于上面的,react官方给出的解决方案是:
1. `shouldComponentUpdate` 检查 props 和 state 的某值是否改变,如果没有改变,则用false来阻止更新。
2. 使用`PureComponent`。
3. 使用React.memo,但它适用于函数组件,不适用class组件。
现在hooks推出了useMemo
示例:
import React , {useState,useMemo} from 'react';
export default function Memo() {
const [count,setCount] = useState(1);
const [val,setVal] = useState('');
function expensive() {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}
return (
<div>
<h4>{count}--{val}--{expensive()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>
)
}
- 创建了两个state,然后通过expensive函数,执行一次昂贵的计算,拿到count对应的某个值。
- 无论是修改count还是val,由于组件的重新渲染,都会触发expensive的执行(能够在控制台看到,即使修改val,也会打印)
- 但这里的昂贵计算只依赖count的值,在val修改的时候,是没有必要再次计算的,在这种情况,可以使用useMemo,只在count的值修改时,执行expensive计算
import React , {useState,useMemo} form 'react';
export default function Memo() {
const [count,setCount] = useState(1);
const [val,setVal] = useState('');
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
},[count]);
return (
<div>
<h4>{count}-{expensive}</h4>
{val}
<div>
<button onClick={() => setCount(count + 1)}>+c1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>
)
}
- 使用useMemo来执行昂贵的计算,然后将计算值返回,并且将count作为依赖值传递进去
- 这样,就只会在count改变的时候触发expensive执行,在修改val的时候,返回上一次缓存的值
# useCallback
useCallback跟useMemo比较类似,但它返回的是缓存的函数
const fnA = useCallback(fnB,[a])
使用场景: 有一个父组件,其中包含子组件,子组件接受一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。
import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
const callback = useCallback(() => {
return count;
},[count])
return (
<div>
<h4>{count}</h4>
<Child callback={callback}/>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)}/>
</div>
</div>
)
}
function Child({ callback }) {
const [count,setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
},[callback]);
return (
<div>{count}</div>
)
}
# memo,useMemo,useCallback的区别
memo
类似于PureCompoent
作用是优化组件性能,防止组件触发重渲染memo
用于于函数组件,PureCompoent
和shouldComponentUpdate
用于类组件useMemo
针对于一段函数逻辑是否重复执行useEffect
是在渲染之后完成的,useMemo
是在渲染期间完成的。useMemo(() => {},[])
,参数如果是空数组的话,只执行一次useCallback(fn)
等价于useMemo(() => {})
# Ref Hooks
- 获取子组件或者DOM节点的句柄
- 渲染周期之间共享数据的存储