React Hooks 从认识到使用_hooks优缺点

前言

HookReact 16.8 的新增特性。它是完全可选的,并且100%向后兼容它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态生命周期钩子等。从概念上讲,React 组件一直更像是函数。而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则。

优点

1、代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护。例如,每个生命周期中常常会包含一些不相关的逻辑。一般我们都会在 componentDidMountcomponentDidUpdate获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关代码却在同一个方法组合在一起。如此很容易产生 bug,并且导致逻辑不一致
2、组件树层级变浅。在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,在 React DevTools 中观察过 React 应用,你会发现由 providersconsumers高阶组件render props 等其他抽象层组成的组件会形成“嵌套地狱”。而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现。
3、不用再去考虑 this 的指向问题。在类组件中,你必须去理解 JavaScript 中 this 的工作方式。

缺点
对一些钩子函数不支持。当下 v16.8 的版本中,还无法实现 getSnapshotBeforeUpdatecomponentDidCatch 这两个在类组件中的生命周期函数。

Hook 规则

  • 不在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们。
  • 不在普通的 JavaScript 函数中调用 Hook,在 React 的函数组件或者自定义 Hook 中调用 Hook。

Hook API

名称 描述
useState 在函数组件中维护自己的状态
useEffect 在函数组件中实现生命周期钩子函数
useContext 用来处理多层级传递数据的方式,减少组件嵌套
useReducer 跟react-redux的使用方式一样,算是提供一个 mini 的 Redux 版本
useCallback 获得一个记忆函数,避免在某些情况下重新渲染子组件,用来做性能优化
useMemo 获得一个记忆组件,和useCallback非常类似,它适用于返回确定的值
useRef 生成对 DOM 对象的引用,它是一个真正的引用,而不是把值拷过去
useImperativeHandle 透传ref,用于让父组件获取子组件内的引用
useLayoutEffect 同步执行副作用,在页面完全渲染完成后,操作DOM

useState

在类组件中,我们使用 this.state来保存组件状态,并对其修改触发组件重新渲染。而在函数组件中,由于没有 this 这个黑魔法,可能通过 useState 来帮我们保存组件的状态。

  • useState(),返回一个 state,以及更新 state 的函数。
  • useState() 中第一个参数是值或者对象,初始渲染期间,返回的状态 (state) 是传入的第一个参数相同。
  • 如果依赖于先前的state,在第二个参数中接收先前的 state,并返回一个更新后的值。
import React, { useState } from "react";
function App() {
  const [count, setCount] = useState(0);	//0是count的默认值
  return (
    <div className="App">
      Count: {count}
      <button onClick={() => setCount((preState=>preState+1))}>+</button>
      <button onClick={() => setCount(0)}>还原</button>
    </div>
  );
}

注意:与类组件中的 setState 方法不同,useState 不会自动合并更新对象。当第一个参数是一个对象时,你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。

import React, { useState } from "react";
function App1(){
  const [obj,setObj]=useState({
    name:'admin',
    age:18
  })
  return(
    <div>
      姓名:{obj.name}-年龄:{obj.age}
      <button onClick={()=>setObj({...obj,name:obj.name+"用户"})}>只修改姓名</button>
    </div>
  )
}

useEffect

语法:useEffect(fn,Array)

  • 第一个参数传递函数,可以用来做一些副作用比如异步请求,修改外部参数等行为。
  • 第二个参数是个数组,数组中的值发生变化才会触发 useEffect 第一个参数中的函数。
  • 如果第二个参数是个空数组的话,默认会在页面加载后执行一次
  • 如果第一个参数有返回值,会在组件销毁或者调用函数前调用。
  • 可以使用useEffect模拟componentDidMountcomponentDidMountcomponentWillUnmount钩子函数。
import React, { useState, useEffect } from "react";
function App(){
	const [count, setCount] = useState(0);
	useEffect(()=>{
	    //只要count有变化,就会执行这里
	},[count])
	
	useEffect(()=>{
	    //如果在下面没有参数的话,页面加载后执行,执行一次
	    return()=>{
	        //页面退出的时候执行
	    }
	},[])
}

useContext

用来处理多层级传递数据的方式,使用 useContext 可以解决 Consumer 多状态嵌套的问题

import React, { useContext } from "react";

const colorContext = React.createContext("gray");
function Bar() {
  const color = useContext(colorContext);
  return <div>{color}</div>;
}
function Foo() {
  return <Bar />;
}
function App() {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  );
}

useReducer

useReducer 这个 Hooks 在使用上几乎跟 React-Redux 一模一样,唯一缺少的就是无法使用 redux 提供的中间件,算是提供一个 mini 的 Redux 版本。

import React, { useReducer } from "react";
const initialState = {
  count: 0
};
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
}
function App() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment", payload: 5 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "decrement", payload: 5 })}>
        -
      </button>
    </>
  );
}

useCallback

通过 useCallback 获得一个记忆后的函数,避免函数组件在每次渲染的时候如果有传递函数的话,重新渲染子组件。用来做性能优化。

import React, { useCallback } from "react";
function App() {
  const memoizedHandleClick = useCallback(() => {
    console.log('Click happened')
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

useMemo

记忆组件,和useCallback类似,不同的是:useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。所以在前面的例子中,可以返回 handleClick 来达到存储函数的目的。

所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。

import React, { useMemo } from "react";
function App() {
  const memoizedHandleClick = useMemo(() => () => {
    console.log('Click happened')
  }, []); // 空数组代表无论什么情况下该函数都不会发生改变
  return <SomeComponent onClick={memoizedHandleClick}>Click Me</SomeComponent>;
}

useRef

跟 createRef 类似,都可以用来生成对 DOM 对象的引用。不同点在于,它是一个真正的引用,而不是把值拷过去。

import React, { useState, useRef } from "react";
function App() {
  let [name, setName] = useState("Nate");
  let nameRef = useRef();
  const submitButton = () => {
    setName(nameRef.current.value);
  };
  return (
    <div className="App">
      <p>{name}</p>

      <div>
        <input ref={nameRef} type="text" />
        <button type="button" onClick={submitButton}>
          Submit
        </button>
      </div>
    </div>
  );
}

useImperativeHandle

透传ref,用于让父组件获取子组件内的引用。

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react";
function ChildInputComponent(props, ref) {
  const inputRef = useRef(null);
  useImperativeHandle(ref, () => inputRef.current);
  return (
    <div>
      <input type="text" name="child input" ref={inputRef} placeholder="我聚焦了"/>
      <input type="text" name="child input1" placeholder="我没有聚焦"/>
    </div>
  )
}
const ChildInput = forwardRef(ChildInputComponent);
function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return (
    <div>
      <ChildInput ref={inputRef} />
    </div>
  );
}

useLayoutEffect

大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。

import React, { useState, useEffect, useLayoutEffect } from "react";
function App() {
  const [width, setWidth] = useState(0);
  useLayoutEffect(() => {
    const title = document.querySelector("#title");
    const titleWidth = title.getBoundingClientRect().width;
    console.log("useLayoutEffect");
    if (width !== titleWidth) {
      setWidth(titleWidth);
    }
  });
  useEffect(() => {
    console.log("useEffect");
  });
  return (
    <div>
      <h1 id="title">hello</h1>
      <h2>{width}</h2>
    </div>
  );
}

在上面的例子中,useLayoutEffect 会在 render,DOM 更新之后同步触发函数,会优于 useEffect 异步触发函数。

参考资料:https://react.docschina.org/docs/hooks-reference.html
https://github.com/happylindz/blog/issues/19

猜你喜欢

转载自blog.csdn.net/Charissa2017/article/details/106730493
今日推荐