React Hooks全解

692 阅读7分钟

最新Hooks教程可看我这篇文章哦:30张图全面剖析React Hooks(十五) - 掘金 (juejin.cn)

1、 React Hooks 介绍

React Hooks是对函数型组件进行增强, 让函数型组件可以存储状态, 可以拥有处理副作用的能力,让开发者在不使用类组件的情况下, 实现相同的功能.

类组件的不足 (Hooks 要解决的问题)

  1. 缺少逻辑复用机制

为了复用逻辑增加无实际渲染效果的组件,增加了组件层级 显示⼗分臃肿 增加了调试的难度以及运行效率的降低

  1. 类组件经常会变得很复杂难以维护

将⼀组相干的业务逻辑拆分到了多个生命周期函数中 在⼀个⽣命周期函数内存在多个不相⼲的业务逻辑

  1. 类成员方法不能保证this指向的正确性

2、React Hooks 使用

Hooks 意为钩子, React Hooks 就是⼀堆钩子函数, React 通过这些钩子函数对函数型组件进⾏增强, 不同的钩子函数提供了不同的功能

seState()

useEffect()

useReducer()

useRef()

useCallback()

useContext()

useMemo()

useState()

使用useState让函数式组件保存状态

import React, { useState } from 'react';

function App() {
  const [count, setCount] = useState(0)
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}

export default App

useState使用细节


1.接收唯一的参数即状态初始值.初始值可以是任意数据类型

2.返回值为数组.数组中存储状态值和更改状态值的方法.方法名称约定以set开头,后面加上状态名称

3.方法可以被调用多次.用以保存不同状态值

4.参数可以是个函数, 函数返回什么,初始状态就是什么,函数只会被调用一次,用在初始值是动态值的情况

import React, { useState } from 'react';

function App(props) {
  // 每次渲染时都会初始化值 无意义 使用useState参数为函数的写法
  // const propsCount = props.count || 0
  const [count, setCount] = useState(() => {
    return props.count || 0
  })
  const [person, setPerson] = useState({name: "张三", age: 22})
  return (
    <div>
      <span>{count}  {person.name} {person.age} {person.sex}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
      {/* 传递一个新的状态值替换老的状态值 */}
      <button onClick={() => setPerson({name:"李四", age: 30})}>setLisi</button>
      {/* 如果只改变一个属性值 前面解构出所有属性 后面是覆盖或者追加的属性 */}
      <button onClick={() => setPerson({...person, name: "王五"})}>setWangwu</button>
    </div>
  )
}

export default App

设置状态值方法的使用细节


设置状态值方法的参数可以是一个值也可以是一个函数

设置状态值方法的方法本身是异步的

import React, { useState } from 'react';

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

  function handleCount() {
    // 传递函数 count 就是要更改的状态 返回更改后的状态
    setCount(count => {
      //document.title = count;
      //return count + 1
      const newCount = count + 1;
      document.title = newCount;
      return newCount;
    })
    // 拿到的是上一次的count 证明设置状态的方法是异步的 可以将这个操作放到函数里
    // document.title = count
  }
  return (
    <div>
      <span>{count}</span>
      <button onClick={handleCount}>+1</button>
    </div>
  )
}

export default App

useReducer()

useReducer是另一种让函数组件保存状态的方式

import React, { useReducer } from 'react';

function App() {
  function reducer(state, action) {
    switch(action.type) {
      case "increment":
        return state + 1
      case "decrement":
        return state - 1
      default:
        return state
    }
  }

  const [count, dispatch] = useReducer(reducer, 0)
  
  return (
    <div>
      <button onClick={() => dispatch({type: "increment"})}>+1</button>
      <span>{count}</span>
      <button onClick={() => dispatch({type: "decrement"})}>-1</button>
    </div>
  )
}

export default App

useContext()

在跨组件层级获取数据时简化获取数据的代码

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

const countCotext = createContext()
function App() {
  return (
    <countCotext.Provider value={100}>
      <Foo />
    </countCotext.Provider>
  )
}

function Foo() {
  // 使用useContext简化操作
  const value = useContext(countCotext)
  return <div>我是Foo组件{value}</div>

  // 消费Provider提供的数据
  // return (
  //   <countCotext.Consumer>
  //     {
  //       value => {
  //         return <div>{value}</div>
  //       }
  //     }
  //   </countCotext.Consumer>
  // )
}

export default App

useEffect()

让函数型组件拥有处理副作用的能力,类似生命周期函数

可以把useEffect看做componentDidMount, componentDidUpdate和componentWilUnmount这三个函数的组合

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom'

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

  // 组件挂载完成和更新完成之后都会执行
  // useEffect(() => {
  //   console.log(333)
  // })

  // 组件挂载完成之后执行
  // useEffect(() => {
  //   console.log(333)
  // }, [])

  // 组件被卸载之前执行
  useEffect(() => {
    return () => {
      console.log("组件被卸载了")
    }
  })
  
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => ReactDOM.unmountComponentAtNode( document.getElementById('root'))}>卸载组件</button>
    </div>
  )
}

export default App

useEffect的第二个参数

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

function App() {
  const [count, setCount] = useState(0)
  const [person, setPerson] = useState({name: "张三"})

  // 只有指定数据发生变化触发useEffect
  useEffect(() => {
    document.title = count
    console.log("count")
  }, [count])

  
  return (
    <div>
      <span>{count} {person.name}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setPerson({name: "李四"})}>setPerson</button>
    </div>
  )
}

export default App

useEffect结合异步函数


useEffect中的参数函数不能是异步函数,因为useEffect函数要返回清理资源的函数,如果是异步函数就变成了返回Promise

import React, { useEffect } from 'react';

function App() {
  useEffect(() => {
    // getData().then(data => console.log(data))
    (async function() {
      const data = await getData()
      console.log(data)
    })()
  }, [])

  function getData() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve("data数据...")
      }, 500)
    })
  }
  
  return <div></div>
}

export default App

useMemo()

useMemo的行为类似Vue中的计算属性,可以监测某个值的变化,根据变化值计算新值

useMemo会缓存计算结果,如果监测值没有发生变化;即使组件重新渲染,也不会重新计算,此行为可以有助于避免在每个渲染上进行昂贵的计算

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

function App() {
  const [count, setCount] = useState(0)
  const [bool, setBool] = useState(true)
  const result = useMemo(() => {
    console.log("useMemo函数执行了")
    return count * 2
  }, [count])

  return (
    <div>
      <span>{count} {result}</span>
      <span>{bool ? "真" : "假"}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <button onClick={() => setBool(!bool)}>setBool</button>
    </div>
  )
}

export default App

memo方法

性能优化,如果本组件中的数据没有发生变化,阻止组件更新.类似类组件中的PureComponent和shouldComponentUpdate

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

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

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <Foo />
    </div>
  )
}

// 无论自己有没有更改,每次父组件更新都会重新渲染
// function Foo() {
//   console.log("Foo组件被重新渲染了");
//   return <div>Foo组件</div>
// }

// 使用memo
const Foo = memo(() => {
  console.log("Foo组件被重新渲染了")
  return <div>Foo组件</div>
})

export default App

useCallback()

性能优化,缓存函数,使组件重新渲染时得到相同的函数实例

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

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

  const resetCount = useCallback(() => {
    setCount(0)
  }, [setCount])

  // 每次App组件渲染都会生成全新resetCount函数传递给Foo组件 导致其重新渲染
  // const resetCount = () => {
  //   setCount(0)
  // }

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>+1</button>
      <Foo resetCount={resetCount}/>
    </div>
  )
}

const Foo = memo((props) => {
  console.log("Foo组件被重新渲染了")
  return (
    <div>
      <div>Foo组件</div>  
      <button onClick={props.resetCount}>resetCount</button>
    </div>
  )
      
})

export default App

useRef()

获取DOM元素对象

import React, { useRef } from 'react';

function App() {
  const container = useRef()

  return (
    <div ref={container}>
      <button onClick={() => console.log(container)}>获取container</button>
    </div>
  )
}

export default App

保存数据(跨组件周期)

即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染

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

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

  let timerId = useRef();
  useEffect(() => {
    timerId.current = setInterval(() => {
      setCount(count => count + 1)
    }, 1000)
  }, [])

  const stopCount = () => {
    console.log(timerId);
    clearInterval(timerId.current)
  }
  return (
    <div>
      <span>{count}</span>
      <button onClick={stopCount}>停止</button>
    </div>
  )
}

export default App

自定义Hook

自定义Hook是标准的封装和共享逻辑的方式

自定义Hook是一个函数,其名称以use开头

自定义Hook其实就是逻辑和内置Hook的组合

一、

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

function App() {
  const [post, setPost] = useState({})

  useEffect(() => {
    axios.get("https://www.fastmock.site/mock/13089f924ad68903046c5a61371475c4/api/post").then(
      res => setPost(res.data)
    )
  }, [])
  
  return (
    <div>
      <div>{post.title}</div>
      <div>{post.body}</div>
    </div>
  )
}

export default App

使用自定义Hook封装

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

function useGetPost() {
  const [post, setPost] = useState({})
  useEffect(() => {
    axios.get("https://www.fastmock.site/mock/13089f924ad68903046c5a61371475c4/api/post").then(
      res => setPost(res.data)
    )
  }, [])
  return [post, setPost]
}

function App() {
  const [post, setPost] = useGetPost()
    
  return (
    <div>
      <div>{post.title}</div>
      <div>{post.body}</div>
      <button onClick={() => setPost({title:"zhangsan", body:123})}>设置文章</button>
    </div>
  )
}

export default App

二、

import React, { useState } from 'react';

function useUpdateInput(initialValue) {
  const [value, setValue] = useState(initialValue)
  return {
    value,
    onChange: event => setValue(event.target.value)
  }
}

function App() {
  const usernameInput = useUpdateInput("");
  const passwordInput = useUpdateInput("");

  const submitForm = event => {
    event.preventDefault();
    console.log(usernameInput.value)
    console.log(passwordInput.value)
  }
   
  return (
    <form onSubmit={submitForm}>
      <input type="text" name="username" {...usernameInput}/>
      <input type="password" name="password" {...passwordInput}/>
      <input type="submit" />
    </form>
  )
}

export default App

React路由Hooks

react-router-dom路由提供的钩子函数

useState实现原理

import React from "react";
import ReactDOM from "react-dom";

let state = []
let setters = []
let stateIndex = 0

function createSetter(index) {
  return function (newState) {
    state[index] = newState
    render()
  }
}

function useState(initialState) {
  state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState
  setters.push(createSetter(stateIndex))
  let value = state[stateIndex]
  let setter = setters[stateIndex]
  stateIndex++
  return [value, setter]
}

function render() {
  stateIndex = 0
  ReactDOM.render(<App />, document.getElementById("root"))
}

function App() {
  const [count, setCount] = useState(0)
  const [name, setName] = useState("张三")
  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>setCount</button>
      <span>{name}</span>
      <button onClick={() => setName("李四")}>setName</button>
    </div>
  );
}

export default App;

useEffect实现原理

import React from 'react';
import ReactDOM from 'react-dom';

function render () {
  effectIndex = 0;
  ReactDOM.render(<App />, document.getElementById('root'));
}

// 上一次的依赖值
let prevDepsAry = [];
let effectIndex = 0;

function useEffect(callback, depsAry) {
  // 判断callback是不是函数
  if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数的第一个参数必须是函数');
  // 判断depsAry有没有被传递
  if (typeof depsAry === 'undefined') {
    // 没有传递
    callback();
  } else {
    // 判断depsAry是不是数组
    if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数的第二个参数必须是数组');
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex];
    // 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
    let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
    // 判断值是否有变化
    if (hasChanged) {
      callback();
    }
    // 同步依赖值
    prevDepsAry[effectIndex] = depsAry;
    effectIndex++;
  }
}

function useReducer (reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch (action) {
    const newState = reducer(state, action);
    setState(newState);
  }
  return [state, dispatch];
}

function App() {
  function reducer (state, action) {
    switch (action.type) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  return <div>
    {count}
    <button onClick={() => dispatch({type: 'increment'})}>+1</button>
    <button onClick={() => dispatch({type: 'decrement'})}>-1</button>
  </div>;
}

export default App;

useReducer实现原理

import React from 'react';
import ReactDOM from 'react-dom';

function useReducer (reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch (action) {
    const newState = reducer(state, action);
    setState(newState);
  }
  return [state, dispatch];
}

function App() {
  function reducer (state, action) {
    switch (action.type) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  return <div>
    {count}
    <button onClick={() => dispatch({type: 'increment'})}>+1</button>
    <button onClick={() => dispatch({type: 'decrement'})}>-1</button>
  </div>;
}

export default App;