Hooks优势
- 函数组件无this问题
- 副作用(绑定事件、网络请求、访问DOM)的关注点分离
复用状态逻辑Custom Hooks
1. State Hooks
1.1 一般写在函数顶部,这里通过setCount更新count
const [count, setCount]
1.2 延迟初始化,只会执行一次
应用场景:值只需要在第一次渲染时使用
const [count, setCount] = useState(() => {
console.log('initial count', props.defaultCount)
return props.defaultCount || 0
})
2. Effect Hooks
使用时机:第一次渲染之后和每次更新之后执行。
优势:
- 提高代码复用 - 代码无需在componentDidMount、componentDidUpdate、componentWillUnmount重复写
- 关注点分离(相关逻辑分离,不相关逻辑包含) - 多个逻辑多个useEffect互不干扰
const [count, setCount] = useState(0)
useEffect(() => {
document.title = count
})
/*
用[]effect 内部的 props 和 state 就会一直拥有其初始值表示,
addEventListener和removeEventListener就只会发生在第一次渲染后了。
如果第二个参有值,React会通过对比值是否相等来判断是否重新执行effect。
*/
useEffect(() => {
document.querySelector('#size').addEventListener('click', onClick, false)
return () => {
document.querySelector('#size').removeEventListener('click', onClick, false)
}
}, [])
return (
<div>
{
count % 2
? <span id="size">size: {size.width}x{size.height}</span>
: <p id="size">size: {size.width}x{size.height}</p>
}
</div>
)
3. Context Hooks
优势
- 让数据在组件树中传递( Consumer替代方案 )
缺点
- 破坏组件独立性
import React, { useState, createContext, useContext } from 'react';
const CountContext = createContext()
function Counter() {
const count = useContext(CountContext)
return (
<h1>{count}</h1>
)
}
function App() {
const [count, setCount] = useState(0)
return (
<div>
<button
type="button"
onClick={() => {setCount(count + 1)}}
>
Click ({count})
</button>
<CountContext.Provider value={count}>
<Counter/>
</CountContext.Provider>
</div>
)
}
4. 使用Memo&Callback Hooks
使用useMemo和useCallback优势
- 两者区别:useCallback省略了顶层函数。参考
- 减少不必要的重复计算,避免资源浪费( 此处通过依赖的参数减少不必要的计算,这里是count )
- useMemo可以依赖另一个useMemo
useMemo和useEffect区别:
- 两者区别:参考
- useEffect执行的是
副作用,在渲染后执行。 useMemo有返回值,返回值直接参与渲染,所以useMemo是在渲染期间完成。 - useMemo能解决DOM发生变化时,不相干的函数不触发
- 注:所有副作用都放useEffect里,所有渲染放useMemo里
(1) Memo、useMemo
Memo:<Foo/>,针对一个组件渲染是否可以重复执行。 参考
useMemo:() => {},针对一段函数逻辑是否重复执行
注意:循环依赖易致浏览器崩溃
import React, { useState, userMemo } from 'react';
function App() {
const [count, setCount] = useState(0)
/*
useMemo、useEffect、useCallback 是否重复执行仅仅判断当前的依赖值与上一次是否一样,
至于其值是数字,是对象,是true,是false都不重要。
显然,当count计算到3的时候,useMemo的第二个参数是[true],当count变成4的时候,
就是 [false]。显然,值发生了改变,所以当count等于3和4的时候,都会触发useMemo重新计算。
*/
const double = useMemo(() => {
return count * 2
}, [count === 3])
const half = userMemo(() => {
return double / 4
}, [double])
return (
<div>
<button
type="button"
onClick={() => {setCount(count + 1)}}
>
Click ({count}), double: ({double})
</button>
<Counter count={count}/>
</div>
)
}
(2) useCallback
作用:相对于useMemo来说省略了顶层函数。useMemo(() => fn) 等价于 useCallback(fn)
const Counter = memo(function Counter(props) {
console.log('Counter render')
return (
<h1 onClick={props.onClick}>{props.count}</h1>
)
})
/*
* App重新渲染后,onClick导致组件被重新渲染
* onClick作为函数不应该每次都不变化
*/
function App() {
const [count, setCount] = useState(0)
const [clickCount, setClickCount] = useState(0)
const double = useMemo(() => {
return count * 2
}, [count === 3])
// const double = useCallback(count * 2, [count === 3])
// 省略顶层函数
// useMemo(() => fn) 等价于 useCallback(fn)
// const onClick = useMemo(() => {
// return () => {
// console.log('Click')
// setClickCount((clickCount) => clickCount + 1)
// }
// }, [])
const onClick = useCallback(() => {
console.log('Click')
setClickCount((clickCount) => clickCount + 1)
}, [])
return (
<div>
<button
type="button"
onClick={() => {setCount(count + 1)}}
>
Click ({count}), double: ({double})
</button>
<Counter count={double} onClick={onClick}/>
</div>
)
}
5. Ref Hooks
关于useRef更详解,这位博主文章可参考:useRef参考。
useRef的作用(可代替老版String Ref, Callback Ref, CreateRef):
(1) 获取子组件或者DOM节点的句柄
import React, { PureComponent, useRef, useCallback } from 'react';
class Counter extends PureComponent {
speak() {
console.log(`now counter is: ${this.props.count}`)
}
render() {
const { props } = this
return (
<h1 onClick={props.onClick}>{props.count}</h1>
)
}
}
function App() {
const counterRef = useRef()
const onClick = useCallback(() => {
counterRef.current.speak()
}, [counterRef])
return (
<Counter ref={counterRef} count="1" onClick={onClick}/>
)
}
export default App;
(2) 渲染周期之间共享数据的存储
state也可以实现,但是会触发重新渲染。 保存普通变量,访问上一次渲染时的数据甚至是state
import React, { useEffect, useState, useRef } from 'react';
function App() {
const [count, setCount] = useState(0)
let it = useRef()
useEffect(() => {
it.current = setInterval(() => {
setCount(count => count + 1)
}, 250)
}, [])
useEffect(() => {
if (count >= 10) {
clearInterval(it.current)
}
})
return (
<div>
<button
type="button"
onClick={() => {setCount(count + 1)}}
>
Click ({count})
</button>
</div>
)
}
...
6. 自定义Hooks
应用场景:共享状态逻辑。
hooks特别擅长用来实现
状态行为的复用,如果你的li组件里面仅仅用来渲染数据,并没有与其它组件相复用的状态逻辑,那么还是建议使用component,毕竟组件可以直接声明在jsx中,而hooks必须手动调用
hooks和组件的区别在于输入输出,相同之处在于都可以返回jsx参与渲染.
例,useSize(),useCount()为自定义hook,其中useCount分享了关于获取size的状态逻辑,而组件不能这样实现。
import React, { useState, useEffect, PureComponent, useRef, useCallback } from 'react';
function useCounter(count) {
const size = useSize()
return (
<h1>{count} {size.width} x {size.height}</h1>
)
}
// 书写:自定义hook以use开头
function useCount(defaultCount) {
const [count, setCount] = useState(defaultCount)
let it = useRef()
useEffect(() => {
it.current = setInterval(() => {
setCount(count => count + 1)
}, 500)
}, [])
useEffect(() => {
if (count >= 10) {
clearInterval(it.current)
}
})
return [count, setCount]
}
function useSize() {
const [size, setSize] = useState({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
const onResize = useCallback(() => {
setSize({
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight
})
})
useEffect(() => {
window.addEventListener('resize', onResize, false)
return () => {
window.removeEventListener('resize', onResize, false)
}
}, [])
return size
}
function App() {
const [count, setCount] = useCount(0)
const Counter = useCounter(count)
const size = useSize()
return (
<div>
<button
type="button"
onClick={() => {setCount(count + 1)}}
>
Click ({count}), {size.width} x {size.height}
</button>
{Counter}
</div>
)
}
...