12-react-Hooks

组件类的缺点

React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)。
下面是一个简单的组件类。

import React, { Component } from "react";

export default class Button extends Component {
  constructor() {
    super();
    this.state = { buttonText: "Click me, please" };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState(() => {
      return { buttonText: "Thanks, been clicked!" };
    });
  }
  render() {
    const { buttonText } = this.state;
    return ;
  }
}

这个组件类仅仅是一个按钮,但可以看到,它的代码已经很"重"了。真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂。

Redux 的作者 总结了组件类的几个缺点。

  • 大型组件很难拆分和重构,也很难测试。
  • 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
  • 组件类引入了复杂的编程模式

函数组件

React 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类。

React 早就支持函数组件,下面就是一个例子。

function Welcome(props) {
  return 

Hello, {props.name}

; }

但是,这种写法有重大限制,必须是纯函数,不能包含状态,也不支持生命周期方法,因此无法取代类。

React Hooks

React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

Hook 这个单词的意思是"钩子"。

React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子。

React 常用Hook

  • useState
  • useEffect
  • useRef
  • useContext
  • useReducer
  • useCallback
  • useMemo

useState

  • useState()用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。
//引入钩子函数
import {useState} from 'react';
const Test = () => {
    // useState的参数为状态初始值
    // useSibtate的返回值是一个数组
    // 数组第一个成员是变量,第二个成员是函数,用来更新状态,约定是set前缀加上状态的变量名
    const [num,setNum] = useState(1)
    return (
        
访问num: {num}
); } export default Test

useEffect副作用钩子

useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。
useEffect() 可以实现生命周期函数的效果

import { useState, useEffect } from 'react';
const Test = () => {
    const [num, setNum] = useState(1)
    const [msg, setMsg] = useState('a')
    const [list, setList] = useState([])

    // 组件初始化自动执行一次,之后每更新一次执行一次
    // 类似 componentDidUpdate
    useEffect(()=>{
        console.log('没有第二个参数,多次执行');
    })

    //等价于 componentDidMount , 只在初始化时执行一次
    useEffect(()=>{
        console.log('第二个参数为空数组,只执行一次');
    },[])

    // 第二个参数为state, 初次执行后,只有该state改变时执行
    useEffect(()=>{
        console.log('第二个参数为state数据,数据改变一次执行一次');
    },[num])

    useEffect(()=>{
        //return的回调 等价于 componentWillUnmount() 组件卸载时
        return ()=>{
            console.log('组件卸载时');
        }
    },[])
    return (
        
访问num: {num}

访问msg: {msg}
); } export default Test

在父组件中实现组件卸载

import Test from './Test'
import { useState } from 'react';

const App = () => {
  const [flag,setFlag] = useState(true)
  return ( 
    
{/* 单击按钮,卸载组件 */} { flag ? : null}
); }

useRef

useRef用来获取DOM元素

import { useRef , useEffect} from "react";

const Test = () => {
    //1. 创建引用实例
    const btnRef = useRef()
    useEffect(()=>{
        //3. 通过实例的current属性即可访问DOM对象
        btnRef.current.focus()
    },[])
    return (
        
{/* 2. 给DOM元素添加ref属性,值为useRef创建的实例 */}
); } export default Test

useContext 共享状态钩子

如果需要在组件之间共享状态,可以使用useContext()

  1. 使用 React Context API,在组件外部建立一个 Context
    Context因为需要其它组件共享,所以要单独导出
export const AppContext = createContext()   
  1. 利用父组件状态钩子,创建共享状态
const [city,setCity] = useState('郑州')   
  1. 提供了一个 Context 对象,这个对象可以被子组件共享

value值为共享状态,值为一个对象


    Test---{city}
    

4、在后代组件中引入Context,

import {AppContext} from './Test'
  1. useContext()钩子函数用来引入 Context 对象,从中获取所需要的状态
import React,{useContext} from 'react';
......
const {city,setCity} = useContext(AppContext)

完整案例

Parent.js

import { createContext, useState } from 'react'
import Child from './Child'
export const AppContext = createContext()
const Parent = () => {
    const [city, setCity] = useState('郑州')
    return (
        
            Parent---{city}
            
); } export default Parent;

Child.js

import GrandSon from './GrandSon'
const Child = () => {
    return ( 
        
Child
); } export default Child;

GrandSon.js

import React,{useContext} from 'react';
import {AppContext} from './Test'

const GrandSon = () => {
    const {city,setCity} = useContext(AppContext)
    return ( 
        
GrandSon-----{city}

); } export default GrandSon;

useReducer

  1. useReducer 是用于提高应用性能的,当更新逻辑比较复杂时,我们应该考虑使用useReducer
  2. useReducer 是 useState的代替方案,用于 state 复杂变化
  3. useReducer 是单个组件状态管理,组件通讯还需要 props
  4. redux 是全局状态管理,多组件共享数据

useReducer 接受一个 reducer 函数作为参数,
reducer 接受两个参数一个是 state 另一个是 action 。然后返回一个状态 count 和 dispath,count 是返回状态中的值,
而 dispatch 可以发布事件来更新 state 。

import { useReducer } from 'react'

// 定义reducter,不像redux传递type
const reducer = (state,action)=> {
    switch(action){
        case 'add':
             return state + 1;
         case 'sub':
             return state - 1;
         default:
             return state;
    }
 }
const Parent = () => {
    // count为变量即状态 , dispatch用来发布事件改变状态 
    // useReducer第二个参数为count的初始值
    const [count, dispatch] = useReducer(reducer,3)
    return (
        
count------{count}
{/* 用dispatch发布事件 */}
); } export default Parent;

useMemo

函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。

useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。

useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo返回缓存的变量,useCallback返回缓存的函数。
反例: 不相关的val改变,expensive也会执行

import {useState} from 'react';
 
export default function WithoutMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');
 
    function expensive() {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }
 
    return 

{count}-{val}-{expensive()}

setValue(event.target.value)}/>
; }

用useMemo解决

import {useState,useMemo} from 'react';
 
 
export default function WithMemo() {
    const [count, setCount] = useState(1);
    const [val, setValue] = useState('');

    // useMemo第一个参数是回调, 第二个是依赖的变量
    const expensive = useMemo(() => {
        console.log('compute');
        let sum = 0;
        for (let i = 0; i < count * 100; i++) {
            sum += i;
        }
        return sum;
    }, [count]);
 
    return 

{count}-{expensive}

{val}
setValue(event.target.value)}/>
; }

useCallback

useCallback跟useMemo比较类似,但它返回的是缓存的函数。

分析下面的案例:
每次修改count,set.size就会+1,这说明useCallback依赖变量count,count变更时会返回新的函数;而val变更时,set.size不会变,说明返回的是缓存的旧版本函数。

import React, { useState, useCallback } from 'react';
 
const set = new Set();
 
export default function Callback() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        console.log(count);
    }, [count]);
    set.add(callback);
    return 

{count}

{set.size}

setVal(event.target.value)}/>
; }

使用场景是:有一个父组件,其中包含子组件,子组件接收一个函数作为props;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助useCallback来返回函数,然后把这个函数作为props传递给子组件;这样,子组件就能避免不必要的更新。

import React, { useState, useCallback, useEffect } from 'react';

export default function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    //观察子组件userEffect在val值改变时不触发
    // const callback = useCallback(() => {
    //     console.log('cb');
    //     return count;
    // }, [count]);

    //观察子组件userEffect在val值改变时触发
    const callback = () => {
        console.log('cb');
        return count;
    };

    return 

{count}

setVal(event.target.value)}/>
; } function Child({ callback }) { console.log('child'); const [count, setCount] = useState(() => callback()); useEffect(() => { console.log('useeffect'); setCount(callback()); }, [callback]); return
子------{count}
}

React-router 常用Hook

  • useParams
  • useHistory
  • useLocation
import { useEffect } from 'react'
import { useParams,useLocation,useHistory }

你可能感兴趣的:(12-react-Hooks)