React Hooks 三部曲 | 8月更文挑战

237 阅读5分钟

1, 为什么会有Hooks?

React组件创建的方式有两种,一种是类组件,一种是纯函数组件,根据组件的设计原理,组件不要变成复杂的容器,最好只是数据流的管道,然后开发者根据需要,组合各种管道即可。也就是说:组件的最佳写法应该是函数,而不是类

但是我们知道,在实际的开发中,类组件和函数式组件的区别是很大的,纯函数组件有着类组件不具备的多种特点,比如:

(1)纯函数组件没有状态

(2)纯函数组件没有生命周期

(3)纯函数组件没有this

(4)只能是纯函数

上面纯函数的特点导致:我们推崇的函数组件只能做UI展示的功能,涉及到状态的管理和切换,我们还是不得不用类组件或者redux, 但我们也知道类组件是有很多缺点的,比如:即使一个简单的页面,你的代码也会显得很重,并且每创建一个类组件,都要去继承一个React实例,至于redux更不用说了,记住一句话,能用React解决的问题就不要用Redux,

比如:一个简单的计数器用类组件实现:

import React from 'react'
class AddCount extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      count:0
    }
  }
  addCount = () => {
    let newCount = this.state.count
    this.setState({
      count: newCount -1
    })
  }
  render() {
    return (
      <>
       <p>{this.state.count}</p>
       <button onClick={this.addCount}>Count--</button>
      </>
    )
  }
}
export default AddCount;

可以看出来,上面的代码很重,为了解决这种,类组件功能齐全却很重,纯函数组件很轻便却又很多重大的限制,这个时候,React团队设计了React Hooks。

React Hooks就是加强版的函数组件, 我们完全可以不用Class的类组件,就可以写出一个功能齐全的函数式组件。

2, 什么是Hooks?

Hooks的意思就是钩子的意思,React Hooks的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部的代码钩进来,Hooks就是我们说的钩子,那么这些钩子怎么用了?你要写什么功能,就用什么钩子,所以常用的钩子函数我们要时刻记住,当然如果有特殊情况,我们可以自定义钩子。

# 常用的钩子
useState()
useContext()
useReducer()
useEffect()

记住:不同的钩子为函数引入不同的外部功能。熟记各种不同钩子的作用。我们发现上面的钩子都是use前缀,React约定,钩子一律使用use前缀命名,所以自己定义的钩子函数也要用useXXX形式命名

3, Hooks的用法

(1)useState()状态钩子

纯函数组件没有状态

useState创建一个状态,赋值一个初始值,当前赋值的初始值为0

数组的第一个是一个变量,此变量指向当前状态的值:this.state

数组的第二个是一个函数,次函数可以修改状态的值: this.setState

import React, { useState } from 'react';
function ExampleHook() {
  const [age, setAge] = useState(20);
  const [sex, sexSex] = useState('男');
  const [work, setWork] = useState('前端程序员')
  return (
    <div>
       <p>zlm今年 { age }</p>
       <p>性别 { sex }</p>
       <p>工作 {work}</p>
    </div>
  )
}
export default ExampleHook;

注意:

1, useState, 靠顺序记忆 2, 不能存在于条件判断语句中

(2)useContext()共享状态钩子

1, 解决父子组件传值, 父组件创建上文对象,

比如: CountContext = createContext()

2, 子组件使用useContext 接受,

const count = useContext(CountContext),

是哪个上下文传递的值,就把哪个值放入useContext中

import React, { useState, createContext, useContext } from 'react';
​
const CountContext = createContext();
function Counter() {
  const count = useContext(CountContext)
  return <h2>{count}</h2>
}
function ExampleHook() {
  const [count, setCount] = useState(0)
  return (
    <div>
       <p>You Clicked { count } times</p>
        <button onClick={()=>{setCount(count + 1)}}>Click me</button>
        <CountContext.Provider count={count}>
            <Counter></Counter>
        </CountContext.Provider>
    </div>
  )
}
export default ExampleHook;

(3) useReducer

1, useReducer 有两个参数,

第一参数是reducer函数, 第二个参数是当前操作的这个state的初始值 2, 第一参数reducer函数,里面也有两个值,第一个是state, 第二个是action,

3, 整个useReducer返回两个参数,第一个是count的值,第二个是dispatch派发器,传递一个action进去

import React, { useReducer } from 'react';
function ReducerDemo() {
   const  [count, dispatch] = useReducer((state, action) => {
     debugger
    switch(action) {
      case 'add':
         return state + 1
      case 'sub':
         return state -1
      default:
         return state
    }
   }, 0)
   return (
     <div>
       <h2>现在的分数是:{count}</h2>
       <button onClick={()=>{dispatch('add')}}>Increment</button>
       <button onClick={()=>{dispatch('sub')}}>Decrement</button>
     </div>
   )
}
export default ReducerDemo;
​
​

注意:通过:useReducer, useContext 实现Redux

(4) useRef

1, 获取DOM元素 2, 保存变量

import React, { useState, useRef, useEffect } from 'react';
​
function Example8() {
   const inputRef = useRef(null)
   const onButtonClick = ()=> {
     inputRef.current.value = 'Hello zlm'
     console.log(inputRef)
   }
   const [text, setText] = useState('zlm')
   const textRef = useRef()
   useEffect(() => {
      textRef.current = text
      console.log(textRef)
   })
   return (
     <>
      <input ref={inputRef} type='text'></input>
      <button onClick={onButtonClick}>在input上展示文字</button>
      <br/>
      <input  value={text} onChange={(e) => {setText(e.target.value)}}></input>
     </>
   )
}
export default Example8;

(5) useEffect

1, useEffect里面是一个匿名方法,useEffect是异步的,有一个延时的动作, 2,useEffect 对应类组件中的componetDidMount, 和componentDidUpdate 这两个生命周期函数 3, useEffect第二个参数,(1) 如果第二个参数为空,只有组件销毁的时候,才会执行解绑函数,如果没有第二个参数,会一直执行解绑函数 useEffect里面的匿名方法中,return 是匿名解绑函数, 4, 当useEffect第二参数不为空的时候,

import React, { useState, useEffect, useCallback } from 'react';
​
function useWinSize() {
  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  })
  const onResize = useCallback(() => {
      setSize({
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      })
  },[])
  useEffect(()=> {
    window.addEventListener('resize', onResize, false)
    console.log('我只想了一次')
    return ()=> {
      window.removeEventListener('resize')
    }
  },[])
  return  size;
}
function Example9() {
  const size = useWinSize()
  return (
    <div>
      页面的size: {size.width} X {size.height}
    </div>
  )
}
export default Example9;

(6)其他钩子

1,必须use开头 2, useCallback 缓存我们的方法, useMemo缓存我们的状态

3useMemo 解决性能问题

shouldCompentUpdate, 组件更新之前
如果父组件更新了,子组件也会跟着一起更新,这个时候就有性能问题
useMemo有两个参数,第一个参数一个匿名方法,第二个参数是控制匿名方法执行的变量,如果参数有改变,就不执行