什么是hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
下边的useState就是一个react-hook,在其下边的是class组件
function HelloWorld(props){
return <h1>{props.name}</h1>;
}
复制代码
import React,{useState} from 'react'
const NumCount =()=>{
const [num,setNum]=useState(0)
return (<div onClick={()=>setNum(num+1)}>{num}</div>)
}
export default NumCount
复制代码
import React from "react";
class NumCountClass extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
render() {
return (
<div
onClick={()=>{this.setState({count:this.state.count+1})}}
>
{this.state.count}
</div>
);
}
}
export default NumCountClass
复制代码
React认为组件不应该是复杂的容器,最好知识数据流的管道,组合管道,数据流经过各种管道处理之后去渲染页面。组件的最佳写法应该是函数,而不是类。 在16.8之前,react里面写的函数式组件不能维护自身state,只是纯函数,用在纯UI组件上比较多。并不能完全满足业务开发需求。新增的hooks Api增加代码的可复用性,弥补无状态组件没有生命周期,没有数据管理的缺陷。这样完全可用函数式组件来代替Class组件。 React Hooks的意思就是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码「钩」进来。 React Hooks 的设计目的,就是加强函数组件,完全不使用"类",就能写出一个全功能的组件。
函数式组件比Class好在哪
- 代码量少。
- 不用考虑this,不需要bind this。在class中随处可见.bind(this)。但也是可用箭头函数来继承this。
- 性能更好(有待验证),在现在浏览器上对比不是很明显。但是React团队更侧重函数式组件,曾说过要对函数式组件性能进行提升。
- 不用考虑复杂的生命周期
- 代码组织更清晰,类组件的业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑
- 类组件引入复杂度更高的render Props 和高阶组件
高阶组件 (HOC)
高阶组件是一种模式,用来重用组件逻辑,并不是React的Api。HOC可以单独拿出来讲一下。浅显的举个例子,不具体深入。
export function HOC(WrappedComponent){
const newProps={name:'cy'}
return (props)=>{
return <WrappedComponent {...props} {...newProps} />
}
}
import {HOC} from './Hoc'
import NumCountClass from './NumCountClass'
export default HOC(NumCountClass)
复制代码
优点
- 逻辑复用。
- Props劫持 代表作 react-router, withRouter。
- 控制渲染 包裹之后可对组件进行节流渲染懒加载等。
缺陷
- HOC需要在已有组件上进行包裹或嵌套,如果过多会出现多层级。
- HOC可以劫持Props,可随意更改,难以追溯
Hooks初始化
组件初始化过程
- 解析组件类型,判断是Function还是class类型,然后执行对应类型的处理方法
- 判断到当前是Function类型组件后,首先在当前组件,也就是fiberNode上进行hook的创建和挂载,将所有的hook api都挂载到全局变量dispatcher上
- 顺序执行当前组件,每遇到一个hook api都通过next将它连接到当前fiberNode的hook链表上
更新过程
useState的挂载mountState阶段会绑定dispatchAction并作为参数返回,其实也就是使用useState时返回的setXXX。 而在dispatchAction中实际上,做了两件事
- 创建update节点,并连接到useState的queue后面,这样每次调用dispatchAction都会在后面连接一个update节点,从而生成一个更新队列。
- 然后根据更新任务的优先级排列任务,最后遍历整个fiber树执行更新操作。
常见的hooks
useState
const [state,setState] = useState(initialState)
复制代码
- useState 有一个参数,该参数可传如任意类型的值或者返回任意类型值的函数。
- useState 返回值为一个数组,数组的第一个参数为我们需要使用的 state,第二个参数为一个setter函数,可传任意类型的变量,或者一个接收 state 旧值的函数,其返回值作为 state 新值。
- 如果数据简单可以根据数据结构将数据放在不同的useState上。
- 如果数据结构复杂可使用useReducer
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init)
复制代码
- useReducer 接收三个参数,第一个参数为一个 reducer 函数,第二个参数是reducer的初始值,第三个参数为可选参数,值为一个函数,可以用来惰性提供初始状态。 这意味着我们可以使用使用一个init函数来计算初始状态/值,而不是显式的提供值。如果初始值可能会不一样,这会很方便,最后会用计算的值来代替初始值。
- reducer方法接收两个参数,一个state,另一个是action。
- useReducer返回一个数组,一个state,一个dispatch,state是返回状态中的值,dispatch是一个可以发布更新state的方法。
import React,{useReducer} from 'react'
const NumCount =()=>{
const [countObj,dispatch]=useReducer((state,action)=>{
if(action.type==='add'){
return {
count:state.count+1
}
}
if(action.type==='subtract'){
return {
count:state.count-1
}
}
},{
count:0
})
return (
<div>
<div>{countObj.count}</div>
<button onClick={()=>dispatch({type:'add'})}>+</button>
<button onClick={()=>dispatch({type:'subtract'})}>-</button>
</div>
)
}
export default NumCount
复制代码
使用场景
- state是数组或对象,并且层级比较深
- state变化很复杂,需要经过复杂的计算
- 一个操作变更多个state
useEffect
effect(副作用):指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。 副作用操作可以分两类:需要清除的和不需要清除的。 如果有一个需求是在组件加载之后,title会随之变化,那么改变title的这个操作就是这个组件的副作用,就可以通过useEffect实现。 可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。
import React, { useEffect } from "react"
const TitleBox=function(){
useEffect(()=>{
document.title='done'
})
return <div>title</div>
}
export default TitleBox
复制代码
useEffect的作用就是指定一个副作用函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副作用函数也会执行。
有时候不需要每次都执行
这时候就用到了useEffect的第二个参数,第二个参数执行副作用函数执行的依赖项,只有这些依赖项更新了才会执行effect的函数。 如果传入空数组则只在组件加载进入DOM后执行一次。
useEffect(()=>{
document.title='done'
},[])
复制代码
有时候需要清除副作用
副作用函数还可以通过返回一个函数来指定如何清除副作用,为防止内存泄露,清除函数会在组件卸载前执行。如果多次渲染,则在下一个effect之前,上一个effect就被清除
import React,{useEffect, useState} from "react"
const ClearEffectBox =()=>{
const [count,setCount]=useState(0)
useEffect(()=>{
const timer=setInterval(()=>{
setCount(count+1)
},1000)
// 如果不清除 每次更新之后就又多了一个计时器
return ()=>{
clearInterval(timer)
}
})
return <div>
timer:{count}
</div>
}
export default ClearEffectBox
复制代码
自定义hooks
如果函数的名字以 use 开头,并且调用了其他的 Hook ,则就称其为一个自定义 Hook 。
useTitle
简单操作页面title hook
import { useEffect } from "react";
function useTitle(title){
useEffect(()=>{
document.title=title
},[title])
}
export default useTitle
useTitle('kkkkkk')
复制代码
useDebounce
简单防抖自定义hook
import { useEffect, useRef } from 'react'
const useDebounce = (fn, ms = 1000, deps = []) => {
let timeout = useRef()
useEffect(() => {
if (timeout.current) clearTimeout(timeout.current)
timeout.current = setTimeout(() => {
fn()
}, ms)
}, deps)
const cancel = () => {
clearTimeout(timeout.current)
timeout = null
}
return [cancel]
}
export default useDebounce
复制代码
import React,{useState} from "react"
import useDebounce from "./useDebounce"
function DebounceBox(){
const [val,setVal]=useState('')
const [disVal,setDisVal]=useState('')
useDebounce(()=>{
setDisVal(val)
},1000,[val])
return <div>
<input onInput={(e)=>{setVal(e.target.value)}} />
disVal:{disVal}
</div>
}
export default DebounceBox
复制代码
小疑问
为什么每次更新都执行Effect?
此默认行为保证了一致性,避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug。
自定义hook必须以use开头吗?
必须的,这是自定义hook的规范,不这么写也能用,但是无法判断这个函数内部是否有hook调用。
有了Hook,函数式组件能完全替代Class组件吗?
我认为是可以的。之前有个项目完全使用hook+function 写的,踩一些坑,但是代码量是真的少很多。
引用:
作者:皮蛋君61304
链接:juejin.cn/post/715943…
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。