React Hooks

317 阅读5分钟

React Hooks 介绍

React是主流的前端框架,v16.8版本引入了全新的的API,叫做React Hooks。React的核心是组件,在v16.8版本之前,组件的标准写法是类(class)。下面是一个简单的类组件:

import React from "react";
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      n: 1,
    };
  }
  onClick = () => {
    this.setState((state) => ({
      n: state.n + 1,
    }));
  };
  render() {
    return (
      <div>
        {this.state.n}
        <button onClick={this.onClick}>+1-1</button>
      </div>
    );
  }
}

这个组件只是实现一个简单的+1操作,代码已经很多了,真实的App有多个类按照层级,一层一层构成,复杂度成倍增加。所以React团队希望,组件不要变成复杂的容器,组件的最佳写法应该是函数,而不是类。

但是函数组件没有state,也没有生命周期,所以React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。

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

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

它包括:

useState

使用状态

const [n,setN] = React.useState(0)
const [user,setUser] = React.useState({name:"leehome"})

注意事项

  1. 如果state是一个对象,不能部分setState,因为setState不会帮我们合并属性
  2. setState(obj)如果obj地址不变,那么React就认为数据没有变化

import React, { useState } from "react";

function App() {
  const [user, setUser] = useState({ name: "joe", age: 18 });
  const onClick = () => {
    setUser({ name: "leehome" });
  };
  return (
    <div>
      <span>{user.name}</span>
      <hr />
      <span>{user.age}</span>
      <hr/>
      <button onClick={onClick}>Click!</button>
    </div>
  );
}


tips

setState优先使用函数形式:setN(i=>i+1)

useReducer

使用useReducer分4步走:

  1. 创建初始值initialStateaction
  2. 创建所有操作reducer(state,action)
  3. 传给useReducer,得到读和写API
  4. 调用写({type:'操作类型'})

总的来说,useReducer是useState的复杂版,代码如下:

import React, {  useReducer } from "react";
import ReactDOM from "react-dom";

const initial = {
  n: 0
};

const reducer = (state, action) => {
  switch(action.type){
 case("add"):
  {
    return { n: state.n + action.number }
  }
 case("multi"): {
    return { n: state.n * 2 };
  } default:{
    throw new Error("unknown type");
  }
}
};

function App() {
  const [state, dispatch] = useReducer(reducer, initial);
  const { n } = state;
  const onClick = () => {
    dispatch({ type: "add", number: 1 });
  };
  const onClick2 = () => {
    dispatch({ type: "add", number: 2 });
  };
  return (
    <div className="App">
      <h1>n: {n}</h1>

      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>+2</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


useContext

上下文

全局变量是全局的上下文

上下文是局部的全局变量

使用方法

  1. 使用C=createContext(initial)创建上下文
  2. 使用<C.provider>赋值圈定作用域
  3. 在作用域内使用useContext(C)来使用上下文

代码示例:

import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const C = createContext(null);

function App() {
  console.log("App 执行了");
  const [n, setN] = useState(0);
  return (
    <C.Provider value={{ n, setN }}>
      <div className="App">
        <Baba />
      </div>
    </C.Provider>
  );
}

function Baba() {
  const { n, setN } = useContext(C);
  return (
    <div>
      我是爸爸 n: {n} <Child />
    </div>
  );
}

function Child() {
  const { n, setN } = useContext(C);
  const onClick = () => {
    setN(i => i + 1);
  };
  return (
    <div>
      我是儿子 我得到的 n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);



useEffect

useEffect(副作用)对环境的改变即为副作用,如修改document.title

为了更好的理解,我们可以把他看作成afterRender,在每次rener后执行

用途

作为componentDidMount使用,[ ]作用第二个参数

作为componentDidUpdate使用,可指定依赖

作为componentWillUnmount使用,通过return

代码示例:

import React, { createContext, useState, useContext } from "react";
import ReactDOM from "react-dom";

function App() {

  const [n, setN] = useState(0);
  const onClick=()=>{
setN(i=>i+1)
}
  useEffect(()=>{
console.log('第一次渲染')//第一次渲染执行
},[])
  useEffect(()=>{
console.log('第一次渲染')/n变化时执行
},[n])  return (
      <div>
      n:{n}
<button onClick={onClick}>+1</button>
      </div>
  );
}


const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useLayoutEffect

useLayoutEffect(布局副作用)在浏览器渲染之前执行


useMemo

要理解React.useMemo,那得先讲下React.Memo,React默认有多余的render,比如:

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

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };

  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
      <Child data={m}/>
    </div>
  );
}

function Child(props) {
  console.log("child 执行了");
  console.log('假设这里有大量代码')
  return <div>child: {props.data}</div>;
}

const Child2 = React.memo(Child);

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

我们发现点击改变n的时候,m依赖的函数组件<Child>也随之执行了

const Child2 = React.memo(Child);

但是我们把<Child>改为<Child2>的时候,就不会出现上面的情况,就避免了多余的render

但是React.Memo有一个bug,添加了监听函数,马上就破功了,

 const onClickChild = () => {
    console.log(m);
  };

  return (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
      <Child2 data={m} onClick={onClickChild} />
      {/* Child2 居然又执行了 */}
    </div>
  );
}

function Child(props) {
  console.log("child 执行了");
  console.log("假设这里有大量代码");
  return <div onClick={props.onClick}>child: {props.data}</div>;
}

因为App运行会在执行函数的时候,生成新的函数,新旧函数虽然功能一样,但是地址不一样。所以我们可以用React.useMemo

 const onClickChild = useMemo(() => {
   }, [m])

这个时候只要依赖值m不变,函数组件Child就不会后执行。

注意:

useMemo第一个参数是()=>value,第二个参数是依赖[m],如果value是一个函数,就得写成: ()=>(x)=>{console.log(x)}

是不是很难用?于是就有了React.useCallback

useCallback(x => log(x), [m])等价于 useMemo(() => x => log(x), [m])

useRef

常见的useRef是引用一个标签元素,这里我就不介绍了

如果你需要一个值,在组件渲染的时候保持不变,你可以用useRef

比如:
初始化:const count =useRef(0)//注意括号里面是{current:0}的缩写,他引用的是个对象

读取:count.current

你还可以用useRef引用一个DOM对象

import React, { useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const buttonRef = useRef();
 
  return (
    <div className="App">
      <Button2 ref={buttonRef}></Button2>
      {/* 看浏览器控制台的报错 */}
    </div>
  );
}

const Button2 = props => {
  return <button className="red" {...props} />;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

我们想引用一个函数组件,(Class组件不报错可直接引用)系统报错:

这个时候我们就要用到forwardRef

import React, { useRef } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const buttonRef = useRef();
  console.log(buttonRef)
  return (
    <div className="App">
      <Button3 ref={buttonRef}>按钮</Button3>
    </div>
  );
}

const Button3 = React.forwardRef((props, ref) => {
  return <button className="red" ref={ref} {...props} />;
});

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

控制台打出: