react hooks基本使用

作者: 小猿_Luck_Boy | 来源:发表于2021-07-23 10:42 被阅读0次

    前言

    在hooks没有出现之前,函数组件基本只做展示功能,涉及到状态管理,我们需要用到class组件或者数据管理框架(redux/dva/mobx);

    小编福利推荐,更多精彩内容请点击链接,点击这里

    hooks组件趋向函数化编程。更简单,组件颗粒度更小,代码也更少。class组件有如下不足:

    问题 解决 缺点
    生命周期复杂,逻辑耦合
    大型组件逻辑难以复用,难以拆分 继承解决 不能多继承
    class组件有this指针

    而 hooks 对这些问题都有较好的解决方案

    • 通过自定义hooks解决多继承
    • 通过自定义 useEffect 来解决复用问题,细分逻辑,减小出现逻辑臃肿的场景

    为了解决这种,类组件功能齐全却很重,纯函数也有重大限制。也就有了React Hooks,也就是加强版的函数组件,我们可以完全不使用 class,就能写出一个全功能的组件。

    hooks常用API讲解

    useState

    hooks 的能力,就是让我们在函数组件中使用 state, 就是通过 useState 来实现的。

    useState 会返回一个数组,第一个值是我们的 state, 第二个值是一个函数,用来修改该 state 的。

    请看下边Demo 在线运行

    import React, { useState } from "react";
    import "./styles.css";
    
    export default function App() {
      const [count, setCount] = useState(0);
    
      const addOne = () => {
        setCount(count + 1);
      };
    
      const addTwo = () => {
        setCount((pre) => pre + 2);
        console.log(count);
      };
    
      return (
        <div>
          点击次数: {count}
          <br />
          <button onClick={addOne}>点我+1</button>
          <br />
          <button onClick={addTwo}>点我+2</button>
        </div>
      );
    }
    

    useState(defaultValue) defaultValue可以一个值,也可以是一个函数,返回一个值。
    也可以setState((previewState => { return newState }))这样来更新状态

    useEffect

    Effect Hook 可以让你在函数组件中执行副作用操作,什么是副作用呢,就是除了状态相关的逻辑,比如网络请求,监听事件,查找 dom

    useEffect支持两个参数,第一个参数是函数

    第二个参数,有三种情况

    • 什么都不传,组件每次 render 之后 useEffect 都会调用,相当于 componentDidMount 和 componentDidUpdate
    • 传入一个空数组 [], 只会调用一次,相当于 componentDidMount 和 componentWillUnmount
    • 传入一个数组,其中包括变量,只有这些变量变动时,useEffect 才会执行

    useEffect 实现生命周期

    useEffect可以看做 componentDidMountcomponentDidUpdatecomponentWillUnmount 这三个声明周期函数的组合。

    请看下边Demo 在线运行

    import React, { useState, useEffect } from "react";
    import "./styles.css";
    
    function Child() {
      const [count, setCount] = useState(0);
      const [width, setWidth] = useState(document.body.clientWidth);
    
      const onChange = () => {
        setWidth(document.body.clientWidth);
      };
    
      useEffect(() => {
        // 相当于 componentDidMount
        console.log("componentDidMount");
        window.addEventListener("resize", onChange, false);
    
        return () => {
          // 相当于 componentWillUnmount
          console.log("componentWillUnmount");
          window.removeEventListener("resize", onChange, false);
        };
      }, []);
    
      useEffect(() => {
        // 相当于 componentDidUpdate
        console.log("componentDidUpdate");
        document.title = count;
      });
    
      useEffect(() => {
        console.log(`count change: count is ${count}`);
      }, [count]);
    
      return (
        <div>
          页面名称: {count}
          <br />
          页面宽度: {width}
          <br />
          <button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            点我
          </button>
        </div>
      );
    }
    
    function App() {
      const [visible, setVisible] = useState(true);
    
      return (
        <div>
          <button
            onClick={() => {
              setVisible(!visible);
            }}
          >
            点我{visible ? "隐藏" : "显示"}
          </button>
          {visible && <Child />}
        </div>
      );
    }
    export default App;
    

    useContext

    context 实例中的 ProviderConsumer,在类组件和函数组件中都能使用,contextType 只能在类组件中使用,因为它是类的静态属性,具体如何使用 useContext

    请看下边Demo 在线运行

    import React, { useState, useContext, Component, createContext } from "react";
    import "./styles.css";
    
    // 创建一个 context
    const Context = createContext(0);
    
    // 组件一, Consumer 写法
    class Item1 extends Component {
      render() {
        return <Context.Consumer>{(count) => <div>{count}</div>}</Context.Consumer>;
      }
    }
    
    // 组件二, contextType 写法
    class Item2 extends Component {
      static contextType = Context;
      render() {
        const count = this.context;
        return <div>{count}</div>;
      }
    }
    // 组件一, useContext 写法
    function Item3() {
      const count = useContext(Context);
      return <div>{count}</div>;
    }
    
    function App() {
      const [count, setCount] = useState(0);
      return (
        <div>
          点击次数: {count}
          <button
            onClick={() => {
              setCount(count + 1);
            }}
          >
            点我
          </button>
          <Context.Provider value={count}>
            <Item1></Item1>
            <Item2></Item2>
            <Item3></Item3>
          </Context.Provider>
        </div>
      );
    }
    
    export default App;
    

    三种写法都能够实现我们的需求,但是,三种写有各自的优缺点,下面为对比出的结果

    写法 优缺
    consumer 嵌套复杂,Consumer 第一个子节点必须为一个函数,无形增加了工作量
    contextType 只支持 类组件,无法在多 context 的情况下使用
    useContext 不需要嵌套,多 context 写法简单

    结论:useContext写法简单,功能丰富

    useMemo

    useMomo一版是用来做优化使用的,可以和Vue的计算属性做对比

    useMemo 也支持传入第二个参数,用法和 useEffect 类似

    1. 不传数组,每次UI更新结束都会重新计算

    2. 空数组,只会计算一次

    3. 依赖对应的值,当对应的值发生变化时,才会重新计算(可以依赖另外一个 useMemo 返回的值)

      请看下边Demo 在线运行

    import { useMemo, useState, useCallback, memo } from "react";
    import "./styles.css";
    
    const Child = memo(({ value }) => {
      console.log("child 渲染", value);
      return <div>{value}</div>;
    });
    
    const Child1 = memo(({ value, add }) => {
      console.log("child1 渲染", value);
      return (
        <div>
          <button onClick={add}>递增m</button>
          {value}
        </div>
      );
    });
    
    export default function App() {
      const [n, setN] = useState(0);
      const [m, setM] = useState(0);
      
      const add = useMemo(() => {
        return () => {
            setM(m + 1);
        }
      }, [m]);
    
      const n1 = useMemo(() => {
        return n + 1;
      }, [n]);
    
      return (
        <div className="App">
          <button
            onClick={() => {
              setN((n) => n + 1);
            }}
          >
            递增n
          </button>
          <Child value={n}></Child>
          <div>n+1={n1}</div>
          {/* <button onClick={add}>新增m</button> */}
          <Child1 value={m} add={add}></Child1>
        </div>
      );
    }
    

    上边使用useMemo返回一个add方法,当其它状态值发生变化时,add方法的指针不会发生变化,也就不会引起Child1组件不必要的更新。如果这里不适用useMemo监听m值依赖变化返回一个方法,而直接使用如下

    const add = () => {
        setM(m+1);
    }
    

    这样当其它状态发生变化的时候,组件重新渲染,函数重新执行,add方法指针发生变化,Child1组件也会更新。所以当我们向子组件传入一个方法的时候尽量使用useMemo的形式。

    useCallBack

    useCallback 是 useMemo 的语法糖。在 react 中我们经常面临一个子组件渲染优化的问题, 尤其是在向子组件传递函数props时,props包含函数时,每次 render 都会创建新函数,导致子组件不必要的渲染,浪费性能。这个时候,就需要使用是 useCallback ,useCallback 可以保证,当依赖值没有变化是,无论 render 多少次,我们的函数都是同一个函数,减小不断创建的开销。

    请看下边Demo 在线运行

    import { useMemo, useState, useCallback, memo } from "react";
    import "./styles.css";
    
    const Child = memo(({ value }) => {
      console.log("child 渲染", value);
      return <div>{value}</div>;
    });
    
    const Child1 = memo(({ value, add }) => {
      console.log("child1 渲染", value);
      return (
        <div>
          <button onClick={add}>递增m</button>
          {value}
        </div>
      );
    });
    
    export default function App() {
      const [n, setN] = useState(0);
      const [m, setM] = useState(0);
      
      const add = useCallback(() => {
        setM(m + 1);
      }, [m]);
    
      const n1 = useMemo(() => {
        return n + 1;
      }, [n]);
    
      return (
        <div className="App">
          <button
            onClick={() => {
              setN((n) => n + 1);
            }}
          >
            递增n
          </button>
          <Child value={n}></Child>
          <div>n+1={n1}</div>
          {/* <button onClick={add}>新增m</button> */}
          <Child1 value={m} add={add}></Child1>
        </div>
      );
    }
    

    参数同useMemo

    useRef

    有两点使用

    1. 获取组件的实例(class组件)

    2. 在函数组件中的一个全局变量,不会因为重复 render 重复申明, 类似于类组件的 this.xxx,(也可以理解UI不依赖的数据)

      请看下边Demo 在线运行

    import React, {
      useMemo,
      useState,
      useRef,
      PureComponent,
      forwardRef
    } from "react";
    import "./styles.css";
    
    const Children1 = forwardRef((props) => {
      const { count } = props;
      return <div>{count}</div>;
    });
    
    class Children extends PureComponent {
      render() {
        const { count } = this.props;
        return <div>{count}</div>;
      }
    }
    
    function App() {
      const [count, setCount] = useState(0);
      const childrenRef = useRef(null);
      const children1Ref = useRef(null);
      const inputRef = useRef(null);
    
      const onClick = useMemo(() => {
        return () => {
          console.log("button click");
          console.log(childrenRef.current.props);
          console.log(children1Ref.current);
          setCount((count) => count + 1);
        };
      }, []);
    
      return (
        <div>
          点击次数: {count}
          <Children ref={childrenRef} count={count}></Children>
          <Children1 ref={children1Ref} count={count} />
          <button onClick={onClick}>点我</button>
          <br />
          <input
            ref={inputRef}
            onChange={() => {
              console.log(inputRef.current.value);
            }}
          />
        </div>
      );
    }
    
    export default App;
    

    useImperativeHandle

    useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值,也就是子组件可以选择性的暴露给父组件一些方法,这样可以隐藏一些私有方法和属性,官方建议,useImperativeHandle应当与 forwardRef 一起使用

    请看下边Demo 在线运行

    import React, {
      useCallback,
      useState,
      useRef,
      useImperativeHandle,
      forwardRef
    } from "react";
    import "./styles.css";
    
    const Child = forwardRef((props, ref) => {
      const introduce = useCallback(() => {
        console.log("子组件 introduce方法");
      }, []);
    
      useImperativeHandle(ref, () => ({
        introduce: () => {
          introduce();
        }
      }));
    
      return <div>子组件{props.count}</div>;
    });
    
    function App() {
      const [count, setCount] = useState(0);
      const childRef = useRef(null);
      const child1Ref = useRef(null);
    
      const onClick = useCallback(() => {
        setCount((count) => count + 1);
        console.log(childRef.current);
        console.log(child1Ref.current);
        childRef.current.introduce();
      }, []);
    
      return (
        <div>
          点击次数: {count}
          <Child ref={childRef} count={count}></Child>
          <button onClick={onClick}>点我</button>
        </div>
      );
    }
    
    export default App;
    

    自定义hook

    自定义hooks可以将组件逻辑复用,颗粒度更小。也是用来解决class组件逻辑难以复用的问题

    自定义hooks规则:

    • use开头
    • 函数内部可以使用hooks提供的api

    请看下边Demo 在线运行

    import React, { useEffect, useState, useCallback } from "react";
    import "./styles.css";
    
    function useMousePosition() {
      const [position, setPosition] = useState({
        x: 0,
        y: 0
      });
    
      const onMouseMove = useCallback((event) => {
        const { clientX, clientY } = event;
        setPosition({
          x: clientX,
          y: clientY
        });
      }, []);
    
      useEffect(() => {
        window.addEventListener("mousemove", onMouseMove, false);
        return () => {
          window.removeEventListener("mousemove", onMouseMove, false);
        };
      }, []);
    
      return position;
    }
    
    function useDomSize() {
      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);
        return () => {
          window.removeEventListener("resize", onResize, false);
        };
      }, []);
    
      return size;
    }
    
    export default function App() {
      const size = useDomSize();
      const position = useMousePosition();
      return (
        <div className="App">
          <h1>
            宽:{size.width}, 高:{size.height}
          </h1>
          <h2>
            x:{position.x}, y:{position.y}
          </h2>
        </div>
      );
    }
    

    相关文章

      网友评论

        本文标题:react hooks基本使用

        本文链接:https://www.haomeiwen.com/subject/zybumltx.html