目录
React hooks的作用就是把无状态组件(只用于展示的组件)升级为函数组件(可以完成类组件的功能)
//类组件
import { Component } from 'react'
class app extends Component {
constructor() {
this.state = {
count: 0
}
}
render() {
let { count } = this.state
return (
<div>
<div> { count } </div>
<button onClick={() => this.setState({ count: count++ })}>点击</button>
</div>
)
}
}
//函数组件
import { useState } from 'react'
function App () {
const [ count, setCount ] = useState(0)
//count 的初始值为0
//setCount 是专门用来设置count值的函数
return (
<div>
<div> { count } </div>
<button onClick={() => { setCount(count + 1)}}>点击</button>
</div>
)
}
//上面两种方式实现的是同样的功能。
1、useState
(请参照上方代码)
使用useState可以使用多个,用来放不同的数据。遵循规范,放在最上面。
//初始值
const [ count, setCount ] = useState(0) 可以写为
const [ count, setCount ] = useState( () => {
你的逻辑代码
return 0
} )
//设置值
在使用setCount时如果值未改变,组件不会重新渲染
setCount可以放一个回调函数
<button onClick={ () => {setCount( a => a+1)} }>点击</button>
其中a表示count上次的值,同样可以完成count+1的功能
2、useEffect
useEffect官方执行副作用的函数。副作用指除了状态相关的逻辑,比如网络请求,监听事件,查找 dom
我理解为就是一个函数,是componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合拳
useEffect 的第二个参数,有三种情况
- 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
- 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
- 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行
function App () {
const [ count, setCount ] = useState(0)
const [ width, setWidth ] = useState(document.body.clientWidth)
const onChange = () => {
setWidth(document.body.clientWidth)
}
useEffect(() => {
// 相当于 componentDidMount
window.addEventListener('resize', onChange, false)
console.log('我只在最开始执行一次')
return () => {
// 相当于 componentWillUnmount
window.removeEventListener('resize', onChange, false)
}
}, [])
useEffect(() => {
// 相当于 componentDidUpdate
console.log(`每次重新渲染我都会执行`)
})
useEffect(() => {
console.log(`count 每次变化我都会执行`)
}, [ count ])
return (
<div>
页面名称: { count }
页面宽度: { width }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
3、useContext
获取上层通过.Provider共享的数据,会一直向上找,没找到就用默认值
// 创建一个 context
const Context = createContext(0)
// 组件一
function Item1 () {
const count = useContext(Context);
return (
<div>{ count }</div>
)
}
// 组件二
function Item2 () {
const count = useContext(Context);
return (
<div>{ count }</div>
)
}
function App () {
const [ count, setCount ] = useState(0)
return (
<div>
点击次数: { count }
<button onClick={() => { setCount(count + 1)}}>点我</button>
<Context.Provider value={count}>
<Item1></Item1>
<Item2></Item2>
</Context.Provider>
</div>
)
}
4、useMemo和useCallback
两者区别不大,类似于Vue中的computed,用于优化性能
function App () {
const [ count, setCount ] = useState(0)
const add = useMemo(() => {
return count + 1
}, [count])
return (
<div>
点击次数: { count }
<br/>
次数加一: { add }
<button onClick={() => { setCount(count + 1)}}>点我</button>
</div>
)
}
useMemo 也支持传入第二个参数,用法和 useEffect 类似
- 不给第二个参数,每次更新都执行
- 空数组,只执行一次
- 有值数组,值变化后执行
需要注意的是,useMemo 会在渲染的时候执行,而不是渲染之后执行,这一点和 useEffect 有区别,所以 useMemo 不建议有 副作用相关的逻辑
useCallback 可以说是 useMemo 的语法糖,能用 useCallback 实现的,都可以使用 useMemo。 在 react 中我们经常面临一个子组件渲染优化的问题,尤其是在向子组件传递函数props时,每次 render 都会创建新函数,导致子组件不必要的渲染,浪费性能,这个时候,就是 useCallback 的用武之地了,useCallback 可以保证,无论 render 多少次,我们的函数都是同一个函数,减小不断创建的开销,具体如何使用看下面例子
const onClick = `useMemo`(() => {
return () => {
console.log('button click')
}
}, [])
const onClick = useCallback(() => {
console.log('button click')
}, [])
同样,useCallback 的第二个参数和useMemo一样,没有区别
5、useRef
- 获取子组件的实例(只有类组件可用)
- 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx
// 使用 ref 子组件必须是类组件
class Children extends PureComponent {
render () {
const { count } = this.props
return (
<div>{ count }</div>
)
}
}
function App () {
const [ count, setCount ] = useState(0)
const childrenRef = useRef(null)
const onClick = useMemo(() => {
return () => {
console.log(childrenRef.current)
setCount((count) => count + 1)
}
}, [])
return (
<div>
点击次数: { count }
<Children ref={childrenRef} count={count}></Children>
<button onClick={onClick}>点我</button>
</div>
)
}
有些情况下,我们需要保证函数组件每次 render 之后,某些变量不会被重复申明,比如说 Dom 节点,定时器的 id 等等,在类组件中,我们完全可以通过给类添加一个自定义属性来保留,比如说 this.xxx, 但是函数组件没有 this,自然无法通过这种方法使用,有的朋友说,我可以使用
useState 来保留变量的值,但是 useState 会触发组件 render,在这里完全是不需要的,我们就需要使用 useRef 来实现了,具体看下面例子
function App () {
const [ count, setCount ] = useState(0)
const timer = useRef(null)
let timer2
useEffect(() => {
let id = setInterval(() => {
setCount(count => count + 1)
}, 500)
timer.current = id
timer2 = id
return () => {
clearInterval(timer.current)
}
}, [])
const onClickRef = useCallback(() => {
clearInterval(timer.current)
}, [])
const onClick = useCallback(() => {
clearInterval(timer2)
}, [])
return (
<div>
点击次数: { count }
<button onClick={onClick}>普通</button>
<button onClick={onClickRef}>useRef</button>
</div>
)
}
当我们们使用普通的按钮去暂停定时器时发现定时器无法清除,因为 App 组件每次 render,都会重新申明一次 timer2, 定时器的 id 在第二次 render 时,就丢失了,所以无法清除定时器,针对这种情况,就需要使用到 useRef,来为我们保留定时器 id,类似于 this.xxx,这就是 useRef 的另外一种用法
6、useReducer
useReducer 是什么呢,它其实就是类似 redux 中的功能,相较于 useState,它更适合一些逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等等的特定场景, useReducer 总共有三个参数
- 第一个参数是 一个 reducer,就是一个函数类似 (state, action) => newState 的函数,传入 上一个 state 和本次的 action
- 第二个参数是初始 state,也就是默认值,是比较简单的方法
- 第三个参数是惰性初始化,这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利
具体使用方法看下面的例子
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, {
count: 0
});
return (
<>
点击次数: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
7、useImperativeHandle
useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。
简单理解:子传父 子组件自定义东西给父组件用
(如果子组件是类组件不需要这么麻烦)
官方建议,useImperativeHandle应当与 forwardRef 一起使用,具体如何使用看下面例子
function Child (props, ref) {
const [num,setNum] = useState(0)
const parentUse = useCallback (() => {
console.log('给父组件调用')
}, [])
//自定义暴露给父组件的实例
useImperativeHandle(ref, () => ({
parentUse : () => {
parentUse()
},
setChildNum : (value) => {
setNum(value)
}
}));
return (
<div > { props.count }</div>
)
}
const ChildDiv = forwardRef(Child)
function App () {
const [ count, setCount ] = useState(0)
const childRef = useRef(null)
const onClick = useCallback (() => {
setCount(count => count + 1)
childRef.current.setChildNum(num => num + count )
childRef.current.parentUse()
}, [])
return (
<div>
点击次数: { count }
<ChildDiv ref={childRef} count={count}></ChildDiv >
<button onClick={onClick}>点我</button>
</div>
)
}
其它hook
自定义hook
自定义 Hook,可以将组件逻辑提取到可重用的函数中,来解决逻辑难以复用问题
function useWidth (defaultWidth) {
const [width, setWidth] = useState(document.body.clientWidth)
const onChange = useCallback (() => {
setWidth(document.body.clientWidth)
}, [])
useEffect(() => {
window.addEventListener('resize', onChange, false)
return () => {
window.removeEventListener('resize', onChange, false)
}
}, [onChange])
return width
}
function App () {
const width = useWidth(document.body.clientWidth)
return (
<div>
页面宽度: { width }
</div>
)
}
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。它的名字应该始终以 use 开头,这样可以一眼看出其符合 Hook 的规则。