React Hooks用法

React Hooks用法

为什么使用Hooks

类组件

类组件需要去继承React.Component并创建render函数来返回react元素
有setState和生命周期(dismount, didupdate, willunmount等)对状态进行管理

缺点:

逻辑复杂的组件难以开发与维护,每个生命周期函数中可能会包含着各种互不相关的逻辑在里面。比如componentDidMount生命周期,需要获取后端数据, 也会需要添加事件监听, 将两个不相关的函数放在了同一个地方, 当项目变得庞大时,容易变得让人难以理解。

-> 1ClassComponent.js

import React from 'react';

class ClassComponent extends React.Component {
  // 初始化类组件的 state
  state = {
    count: 0
  };
  // 编写生命周期方法 didMount
  componentDidMount() {
    // ...业务逻辑
    this.setState({
      count: this.state.count + 1
    })
  }
  // 编写自定义的实例方法
  addNumber = () => {
    // 更新 state
    const newCount = this.state.count + 1;
    this.setState({
      count: newCount
    });
  };
  // 编写生命周期方法 render
  render() {
    return (
      <div>
        <h2>类组件写法</h2>
        <div className="demoClass">
          <div style={{ marginBottom: 20 }}>you clicked {this.state.count} times</div>
          <button onClick={() => this.addNumber()}>click me</button>
        </div></div>
    );
  }
}

export default ClassComponent;

函数式组件

函数式组件 本质上是一个常规函数(纯函数),接受一个参数props, 并返回一个reactElement
缺点

  • 函数式组件没有this和生命周期函数
  • 没有state机性能数据状态管理
import { useState } from 'react';

// 函数式写法
// useState
function Example() {
  // number 为state读取值, setNumber为派发更新的函数
  const [number, setNumber] = useState(0); // 0为初始值

  // // 入参可以是函数, 处理复杂的逻辑 返回值是初始值
  // const flag = 1
  // const [number, setNumber] = useState(() => {
  //   return flag === 1 ? 10 : 100
  // })

  return (
    <div>
      <h2>函数式写法 / useState</h2>
      <p>You clicked {number} times</p>
      <button onClick={() => {
        setNumber(number + 1)
        console.log('number', number) // number的值不是即时改变的, 当下次下上文执行式state才会改变
      }}>click me</button>
    </div>
  )
}


export default Example;

引入hooks

在react类组件(class)写法中,有setState和生命周期对状态进行管理,但是在函数组件中不存在这些,故引入hooks(版本:>=16.8),使开发者在非class的情况下使用更多react特性。

Hooks详解

useState

入参 具体值或者一个函数
返回值 数组 第一项是state值, 第二项负责派发数据更新, 组件渲染

// hooks
const [number, setNumber] useState(0)

// 等价于class
this.state.number
this.setState({
  number: newNumber
})

-> 2UseStateDemo.js

useRef

useRef相当于在函数式组件中添加了一个实例对象,通过refName.current保证获取到的数据肯定是最新的,类似于类组件的this

返回的ref对象在组件的整个生命周期内保持不变

当更新current值时并不会re-render,这是与useState不同的地方

应用场景:获取元素, 缓存数据

// useRef
function Example() {
  const inputRef = useRef(null); // 入参为初始值

  // 操作当前dom元素
  const focusInput = () => {
    console.log('focus current dom');
    inputRef.current.focus();
  }

  // 获取表单元素的值
  const handleSubmit = () => {
    console.log('submit content', inputRef.current.value)
  }

  return (
    <div>
      <h2>useRef</h2>
      <input type='text' ref={inputRef} />
      <button onClick={() => { focusInput() }}>focus me</button>
      <button onClick={() => { handleSubmit() }}>submit it</button>
    </div>
  )
}

useEffect

  • 当组件init、 dom render完成、操作dom、请求数据时调用,类似 类组件生命周期功能
  • 数据加载完成时一定会执行一次(didmount), 若入参数据改变, 也会执行
  • 若不限制条件,组件每次更新都会触发useEffect
  • useEffect 的第一个参数为处理时间, 第二个参数为接受数组(限定条件), 当时数组数据变化时触发事件,为[]则只在组件初始化时触发(相当于componentDisMount)
  • uesEffect 无法直接使用async/await。但是可以在回调函数内部重新写一个async函数,然后调用
    理由是 effect function 应该返回一个销毁函数(effect:是指return返回的cleanup函数),如果 useEffect 第一个参数传入 async,返回值则变成了 Promise,会导致 react 在调用销毁函数的时候报错 :function.apply is undefined。
  • cleanup函数, 执行完成后的销毁函数, 清除事件监听、定时器之类的函数
import { useState, useEffect, useRef } from 'react';

// 模拟数据交互 从后台获取信息
function getUserInfo(name) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({
        age: 18,
        name,
      })
    }, 300)
  })
}

// useEffect
function Example({ name }) {
  const [userMessage, setUserMessage] = useState({})
  const firstRenderRef = useRef(true);

  const [ number , setNumber ] = useState(0);

  useEffect(() => {
    // StrictMode模式会调用两次, 所以使用ref保证初始化时仅调用一次
    if(firstRenderRef.current){
      firstRenderRef.current = false;
      return;
    }
    // 副作用函数中执行具体的请求逻辑
    getUserInfo(name).then(res => {
      console.log('UseEffect:', res);
      setUserMessage(res)
    })

    // async/await写法
    // const getUserInfoAsync = async () => {
    //   const result = await getUserInfo(name)
    //   setUserMessage(result);
    //   console.log('UseEffect Async', result);
    // };
    // getUserInfoAsync();

    // 定义一个计时器
    // const timer = setInterval(()=> console.log('number '+number), 2000)

    // 清除副作用函数 cleanup 
    return function(){
      // console.log('stop');
      // clearInterval(timer)
    }

  }, [name, number]) // 只有在props->name / state->number改变的时候会触发useEffect函数的执行

  return (
    <div>
      <h2>useEffect</h2>
      <div>name {userMessage.name}</div>
      <div>age {userMessage.age}</div>
      <div>number {number}</div>
      <button onClick={() => {
        setNumber(number+1)
      }}>click me</button>
    </div>
  )

}

export default Example;

useLayoutEffect

作用渲染更新之前的useEffect

  • useEffect 组件更新挂载完成-> 浏览器dom绘制完成 -> 执行useEffect回调 === 渲染组件时会闪动
  • useLayoutEffect 组件更新挂载完成-> 执行useLayoutEffect回调-> 浏览器dom绘制完成 ===渲染组件时卡顿

useContext

方便组件间传值, 用来获取父组件传递过来的context值,当前值即最近父组件Provider的value

import { useState, useContext, createContext } from 'react';

//创建context 它返回一个具有两个值的对象 {Provider, Consumer}
const nameContext = createContext(null);
const colorContext = createContext(null);

// useContext 
function Example() {
  return (
    //  colorContext.Provider 创建一个局部作用域 为所有子孙提供value值
    <colorContext.Provider value={'red'}>
      <nameContext.Provider value={'BBB'}>
        <h2>useContext</h2>
        <ChildA />
        <ChildB />
      </nameContext.Provider>
    </colorContext.Provider>
  )

  // // 将值和修改值的方式都传递给子组件
  // const [color, setColor] = useState("red");
  // return (
  //   <colorContext.Provider value={{color, setColor}}>
  //     <h2>useContext</h2>
  //     <ChildC/>
  //   </colorContext.Provider>
  // )
}

function ChildA() {
  //使用Consumer从上下文获取value
  return (
    <colorContext.Consumer>
      {
        (color) => (
          <nameContext.Consumer>
            {name => (
                <div>not useContext: my name is {name} and color is {color}</div>
            )}
          </nameContext.Consumer>
        )
      }

     </colorContext.Consumer>
  )
}
function ChildB() {
  //调用useContext,传入createContext获取的上下文对象。
  const name = useContext(nameContext);
  const color = useContext(colorContext);
  return (
    <div>
      useContext: my name is {name} and color is {color}
    </div>
  )
}

function ChildC() {
  const { color, setColor } = useContext(colorContext);

  return (
    <colorContext.Provider value={{ color, setColor }}>
      <div>
        <p>{color}</p>
        <button onClick={() => (setColor('green'))}>修改颜色</button>
      </div>
    </colorContext.Provider>
  )
}

export default Example;


useReducer

类似redux, 它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
入参

  • 函数 可以视为reducer, 包括state和action, 返回值是根据action的不同而改变后的state

  • state的初始值

出参

  • 更新后的state值
  • 派发更新的dispatch函数, 执行dispatch会导致部分组件re-render
import { useReducer } from 'react';
 
const initialState = { count: 0 };

// 第二个参数:state的reducer处理函数
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 Example() {
  // 返回值:最新的state和dispatch函数
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <h2>useReducer</h2>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  );
}
export default Example;

useMemo

执行函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
缓存的结果是回调函数中return回来的值

import { useState, useMemo, useEffect } from 'react';
function Example() {
  // 这里维护了两个state,
  // 可以看到getNum的计算仅仅跟count有`关,
  // 但是现在无论是count还是val变化,都会导致getNum重新计算,
  // 所以这里我们希望val修改的时候,不需要再次计算`,这种情况下我们可以使用useMemo
  const [count, setCount] = useState(1);
  const [val, setValue] = useState('');


  // // 不使用useMemo的写法
  const getNum = () => {
    console.log('getNumber');
    return count
  }

  // // useEffect 只是限制了触发条件,在进行DOM的操作(setState)后仍然会触发getNum函数
  // useEffect(()=>{
  //   console.log('useEffect:', count);
  // },[count])

  // 使用useMemo
  // const getNum = useMemo(() => {
  //   console.log('getNumber');
  //   return count
  // }, [count])



  return <div>
    <h2>useMemo</h2>
    <div>
      result {getNum()}
      <button onClick={() => setCount(count + 1)}>+1</button>
      <input value={val} onChange={event => setValue(event.target.value)} />
    </div>
  </div>;
}
export default Example;

useCallback

useCallback与useMemo类似, 第一个参数是一个回调函数, 第二个参数是依赖的数据
不同的地方在于useCallback 缓存的结果是函数

父组件传递一个函数给子组件的时候,由于父组件的更新会导致该函数重新生成从而传递给子组件的函数引用发生了变化,这就会导致子组件也会更新,而很多时候子组件的更新是没必要的,所以我们可以通过useCallback来缓存该函数,然后传递给子组件

import { memo, useCallback, useState } from 'react'


// 父组件传递一个函数给子组件的时候,由于父组件的更新会导致该函数重新生成
// 从而传递给子组件的函数引用发生了变化,这就会导致子组件也会更新.
// 而很多时候子组件的更新是没必要的,所以我们可以通过`useCallback`来缓存该函数,然后传递给子组件

const DemoChildren = memo(function ({ getInfo }) {
  console.log('子组件更新');
  return <div>总和:{getInfo()}</div>
})

const Example = ({ id }) => {
  const [number, setNumber] = useState(1);
  const [value, setValue] = useState(10);
  // // 不使用useCallback
  const getInfo = ()=> {
    return number;
  }
  // 使用useCallback
  // const getInfo = useCallback(() => {
  //   return number;
  // }, [number]);

  return <div>
    <h2>useCallback</h2>
    <button onClick={() => setNumber(number + 1)} > 增加 {number}</button>
    <button onClick={() => setValue(value - 1)} > 减少 {value}</button>

    <DemoChildren getInfo={getInfo} />
  </div>
}

export default Example
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值