React Hooks学习笔记

React Hooks 是在 2018年 react 版本16.8.0的时候发布的,到目前已经不是什么新鲜的技术了,现在react版本已经到了17.0.2,但是我相信还有很多人跟我一样对React Hooks只有耳闻,一直没有很了解究竟是什么东东,如何在项目中运用,现在就一起来认识一下吧。

在React Hooks 出现之前,我们把react组件分为类组件和无状态组件,类组件就是class组件,可以定义state,有生命周期,用于处理复杂业务;无状态组件就是纯函数,主要用于页面渲染,没有生命周期与state,也没有复杂的this。

Hooks的出现,组件现在只分为类组件和函数组件,都可以定义state和副作用,我们常说的网络请求,DOM操作,事件的监听与解绑等都可称为副作用,类组件的副作用是在各个生命周期函数里面执行,而Hooks没有类组件的生命周期,但是有用于替代生命周期的hooks钩子。

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。(官网)

Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。(官网)

目录

1.State Hook

2.Effect Hook

3.Context Hooks

4.useMemo

5.useCallback

7.useRef

8.自定义Hook

9.Hook 使用规则


1.State Hook

React 提供了一些内置 Hooks,我们先来学习State Hooks,useState。下面我们分别用类组件和Hook来实现一个计数器。

import React ,{ Component } from 'react';

class App extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            count: 0
        }
    }
    render(){
        let { count } = this.state;
        return (
            <div>
                <p>You clicked {count} times</p>
                <button onClick={() => this.setState({count: count + 1})}>
                    Click me
                </button>
            </div>
        )
    }
}
import React ,{ useState } from 'react';

function App(){
    const [count,setCount] = useState(0);
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    )
}

通过对比可看出,Hook的实现代码会更简洁。useState就是一个Hook函数,它的唯一参数就是初始化state,可以是数字,字符串,对象,数组或者是函数,这个初始化state只有在第一次渲染会用到。useState会返回一对值,当前状态和一个让你更新它的函数,类似类组件的setState,返回值的变量命名是任意的,但是更新函数基本都是以set开头。

如果state的初始值来自于props,那么初始化state可以用函数,就不会在每次组件重渲染时都运行,如下:

import React ,{ useState } from 'react';
function App(props){
    const [count,setCount] = useState(() => {
        return props.index
    });
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    )
}

根据业务需要,我们有时候需要声明多个state,如下:

function App(props){
    const [count,setCount] = useState(0);
    const [name,setName] = useState('tom');
    const [todos,setTodos] = useState([{text: 'learn hooks'}])
    
    ...
}

2.Effect Hook

useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力。它跟 class 组件中的 componentDidMountcomponentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。(官网)

下面我们分别用类组件和Hook实现标题实时更新点击次数和屏幕大小事件监听。

import React ,{ Component } from 'react';

class App extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            count: 0,
            view: {
                height: document.documentElement.clientHeight,
                width: document.documentElement.clientWidth
            }
        }
    }
    resize = () => {
        this.setState({
            view: {
                height: document.documentElement.clientHeight,
                width: document.documentElement.clientWidth
            }
        })
    }
    componentDidMount(){
        document.title = this.state.count;
        window.addEventListener('resize',this.resize)
    }
    componentDidUpdate(){
        document.title = this.state.count;
    }
    componentWillUnmount(){
        window.removeEventListener('resize',this.resize)
    }
    render(){
        let { count, view } = this.state;
        return (
            <div>
                <p>You clicked {count} times</p>
                <button onClick={() => this.setState({count: count + 1})}>
                    Click me
                </button>
                <p>宽:{view.width}  高:{view.height}</p>
            </div>
        )
    }
}
import React ,{ useState, useEffect } from 'react';
function App(){
    const [count,setCount] = useState(0);
    const [view,setView] = useState({
        height: document.documentElement.clientHeight,
        width: document.documentElement.clientWidth
    })

    useEffect(()=>{
        document.title = count;
    })

    const resize = () => {
        setView({
            height: document.documentElement.clientHeight,
            width: document.documentElement.clientWidth
        })
    }

    useEffect(()=>{
        window.addEventListener('resize',resize);
        return () => {
            window.removeEventListener('resize',resize)
        }
    })
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={count => setCount(count + 1)}>
                Click me
            </button>
            <p>宽:{view.width}  高:{view.height}</p>
        </div>
    )
}

在上面的class中,我们分别在 componentDidMount 和 componentDidUpdate 生命周期函数中编写了相同的设置title的代码,因为我们需要在组件加载和更新执行同样的操作。

在上面的Hook中,我们编写了两个 useEffect ,每一个effect处理一个副作用。第一个effect相当于类组件中的 componentDidMount 和 componentDidUpdate 的组合函数;第二个effect相当于 componentDidMount 和 componentWillUnmount 的组合函数。这两个effect在每次渲染都会执行。

useEffect 函数返回一个函数,React将会在执行清除操作时调用它,React会在组件卸载时执行清除操作。

useEffect 函数的第二个入参是一个数组,如果不传入第二个参数,useEffect 在组件每次更新都会触发;第二个参数传入 [] 空数组,effect 只会在初始化的时候触发,组件重渲染都不会触发这个effect。

useEffect(()=>{
  console.log('run')
},[count])

上面这个示例中,我们传入 [count] 作为第二个参数。这个参数是什么作用呢?如果 count 的值是 5,而且我们的组件重渲染的时候 count 还是等于 5,React 将对前一次渲染的 [5] 和后一次渲染的 [5] 进行比较。因为数组中的所有元素都是相等的(5 === 5),React 会跳过这个 effect,这就实现了性能的优化。

当渲染时,如果 count 的值更新成了 6,React 将会把前一次渲染时的数组 [5] 和这次渲染的数组 [6] 中的元素进行对比。这次因为 5 !== 6,React 就会再次调用 effect。如果数组中有多个元素,即使只有一个元素发生变化,React 也会执行 effect。

3.Context Hooks

我们知道 context 可以跨组件树传递数据,函数组件也有 useContext 来替代类组件的 contextType

import React ,{ Component, createContext, useContext } from 'react';

const countContext = createContext();

class Foo extends Component {
    render(){
        return (
            <countContext.Consumer>
                {
                    count => <h1>count: {count}</h1>
                }
            </countContext.Consumer>
        )
    }
}

class Bar extends Component {
    static contextType = countContext;
    render(){
        const count = this.context;
        return <h1>count: {count}</h1>
    }
}

function Counter(props) {
    const count = useContext(countContext);
    return <h1>count: {count}</h1>
}

class App extends Component {
    constructor(props){
        super(props)
        this.state = {
            count: 0
        }
    }
    render(){
        return (
            <div>
                <button onClick={()=> this.setState({ count: this.state.count + 1})}>add</button>
                <countContext.Provider value={this.state.count}>
                    <Foo />
                    <Bar />
                    <Counter />
                </countContext.Provider>
            </div>
        )
    }
}

useContext 接收一个 context 对象,并返回该 context 的当前值。调用了 useContext 的组件总会在 context 值变化时重新渲染。

4.useMemo

useMemo 的语法类似于 useEffect,接收两个参数,一个是函数,一个数依赖数组,如果省略第二个参数,则useMemo每次都会执行,我们使用useMemo是为了优化性能,所以基本不会省略第二个参数;第二个参数为空数组 [],则 useMemo 只会运行一次;第二个参数为依赖变量的数组,则变量改变,useMemo 会重新执行。useMemo 和 useEffect 不同的是,useMemo 是组件渲染过程中执行的,计算结果可参与渲染,而 useEffect 主要是执行副作用,是渲染完成之后执行的。

import React ,{ Component, memo , useMemo, useState } from 'react';

const Counter = memo(function Counter(props) {
    console.log('counter render');
    return <h1>double: {props.count}</h1>
})

function App() {
    const [count, setCount] = useState(1)

    const double = useMemo(() => {
        return count * 2
    })

    return (
        <div>
            <p>count: {count}</p>
            <button onClick={()=> setCount(count => count+ 1)}>add</button>
            <Counter count={ double }/>
        </div>
    )
}

export default App;

 以上 useMemo 第二个参数没有传,则 count 每次改变,double 都会重新计算,再看下面例子:

const double = useMemo(() => {
   return count * 2
},[count == 3])

以上只有count = 3 和 count = 4, double才会重新计算 ,因为当 count 的值为 1和2时,依赖值都为false没有改变,当 count = 3时依赖值为true,发生改变重新计算,count = 4时依赖值变为false,重新计算。

memo 是减少组件不必要的渲染从而优化性能,useMemo则是减少组件内的逻辑重新执行从而优化性能。

5.useCallback

import React ,{ Component, memo , useMemo, useState } from 'react';

const Counter = memo(function Counter(props) {
    console.log('counter render');
    return <h1 onClick={() => props.onClick()}>double: {props.count}</h1>
})

function App() {
    const [count, setCount] = useState(1)

    const double = useMemo(() => {
        return count * 2
    },[count == 3])

    const onClick = () => {
        
    }

    return (
        <div>
            <p>count: {count}</p>
            <button onClick={()=> setCount(count => count+ 1)}>add</button>
            <Counter count={ double } onClick={onClick}/>
        </div>
    )
}

以上例子给 Counter 组件传过去一个事件处理函数,count 每次改变都会生成一个新的函数,Counter都会重新渲染,即使double没有改变。下面使用useMemo来优化:

const onClick = useMemo(() => {
  return () => {
            
  }
},[double])

使用useMemo,返回一个函数,第二个参数为 double,则只会在 double 变化才重渲染 Counter 组件。也可以使用useCallback,如下:

import React ,{ Component, memo , useMemo, useState, useCallback } from 'react';
const onClick = useCallback(() => {
        
},[double])

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)

7.useRef

在类组件中,我们常用的string ref,createRef 获取Dom节点,这些都不适用于函数组件,React 16推出了 useRef 可获取DOM 节点。

import React ,{ Component, useState, useRef } from 'react';

function App() {
    const [count, setCount] = useState(1)
    const refEl = useRef(null)
    const getRef = () => {
        console.log(refEl.current);
    }
    return (
        <div>
            <p ref={refEl}>useRef</p>
            <button onClick={()=> getRef()}>add</button>
        </div>
    )
}

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。 

然而,useRef() 比 ref 属性更有用。它可以很方便地保存任何可变值,其类似于在 class 中使用实例字段的方式。

function App() {
    const [count, setCount] = useState(0)
    let timer;

    useEffect(()=>{
        timer = setInterval(()=>{
            setCount(count => count + 1)
        },1000)
    },[])

    useEffect(()=>{
        if(count >= 10){
            clearInterval(timer)
        }
    })

    return (
        <div>
            <p>{count}</p>
        </div>
    )
}

以上我们设置了一个定时器,但是 count >= 10 时,并没有清除定时器,因为每次渲染都会生成一个新的 timer 变量,所以 clearInterval(timer) 已经不是创建时的那个 timer 变量了,useRef 可以解决这个问题。

   let timer = useRef();

    useEffect(()=>{
        timer.current = setInterval(()=>{
            setCount(count => count + 1)
        },1000)
    },[])

    useEffect(()=>{
        if(count >= 10){
            clearInterval(timer.current)
        }
    })

useRef 可在渲染周期之间共享数据的存储。 

8.自定义Hook

自定义 Hook 是一个函数,函数名称以“use”开头,函数内部可以调用其他的 Hook。下面是一个自定义Hook的实例。

function useResize(){
    const [view,setView] = useState({
        height: document.documentElement.clientHeight,
        width: document.documentElement.clientWidth
    })

    const resize = () => {
        setView({
            height: document.documentElement.clientHeight,
            width: document.documentElement.clientWidth
        })
    }

    useEffect(()=>{
        window.addEventListener('resize',resize);
        return () => {
            window.removeEventListener('resize',resize)
        }
    })

    return view;

}

上面这个自定义 Hook 用于设置窗口大小,返回一个对象,属性为height和width。那么下面看一下如何使用自定义 Hook.

function App(){
    const view = useResize();

    return (
        <div>
            <p>宽:{view.width}  高:{view.height}</p>
        </div>
    )
}

这段代码和原来实例的代码完全一样,我们只是将两个函数之间一些共同的代码提取到单独的函数中。

  • 自定义Hook必须以 use 开头,否则React 将无法自动检查你的 Hook 是否违反了 Hook 规则。
  • 在两个组件中使用相同的自定义Hook不会共享state,自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
  • 每次调用 Hook 都会获取独立的 state

9.Hook 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

  • 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
  • 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。(还有一个地方可以调用 Hook —— 就是自定义的 Hook 中)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值