React Hooks 学习指南

261 阅读6分钟

react的新特性

context

用于一个组件向下传递变量和方法,这个组件的所有子组件可以拿到传递的变量和方法。context不好处就是让组件失去了独立性变得不纯粹,不能复用,用时需注意要用在独立的组件身上。

createContext

父组件

provider

父组件

consumer

子组件

可使用多层context,一个组件中多层context顺序不重要,provider和consumer的顺序都不重要。

contextType

consumer的使用太过于繁杂,contextType可以解决在只有一个context简化consumer的写法。

子组件

lazy、suspense

lazy

lazy 利用webpack实现异步加载,会生成一个chunk.js来加载组件

import React, { Component, lazy, Suspense } from "react";
// import { connect } from 'react-redux'
// lazy 利用webpack实现异步加载,会生成一个chunk.js来加载组件
// /*webpackChunkName:"about"*/是将加载about的chunk.js重命名
const About = lazy(() => import(/*webpackChunkName:"about"*/ "./page/About"));

/webpackChunkName:"about"/是将加载about的chunk.js重命名

命名before

命名after

suspense

Suspense 可以再lazy异步加载组件时是实现一个加载ui,lazy和suspense一起使用,只使用lazy的话会报错的。

会在about加载过程中,ui渲染fallback中的内容

export class App extends Component {
  render() {
    return (
      <div>
        <Suspense fallback={<div>loading</div>}>
          <About />
        </Suspense>
      </div>
    );
  }
}

Suspense 可以再lazy异步加载组件时是实现一个加载ui,lazy和suspense一起使用,只使用lazy的话会报错的

但是当异步加载组件失败的话并不会渲染suspense的fallback中的LoadingUI,

需要利用react中的ErrorBoundary

ErrorBoundary

它是用来捕获视图加载失败的,利用的是react中一个声明周期方法componentDidCatch

export class App extends Component {
  state = {
    hasError: false,
  };

  componentDidCatch() {
    this.setState({ hasError: true });
  }

  render() {
    if (this.state.hasError) {
      return <div>error了</div>;
    }
    return (
      <div>
        <Suspense fallback={<div>loading</div>}>
          <About />
        </Suspense>
      </div>
    );
  }
}

blockURL之后,about组件加载失败,componentDidCatch将hasError置为true,渲染error了

ErrorBoundary的另一种写法

不使用componentDidCatch而是会用 static getDerivedStateFromError,一旦发生错误,他就会返回一个新的state并合并到state中。

export class App extends Component {
  state = {
    hasError: false,
  };
  static getDerivedStateFromError() {
    return {
      hasError: true,
    };
  }

  render() {
    if (this.state.hasError) {
      return <div>error了</div>;
    }
    return (
      <div>
        <Suspense fallback={<div>loading</div>}>
          <About />
        </Suspense>
      </div>
    );
  }
}

memo

当父组件重新渲染子组件也会跟着重新渲染,所以当我们不希望子组件做一些不必要的重新渲染动作时。可以使用memo。

子组件使用memo之后只有所依赖其父组件中值发生变化,才会重新渲染子组件,否则子组件不会发生重新渲染。

memo与purComponent有着一样的效果。

ReactHooks

比较与class类组件的好处:

useState

useState是按照次序声明来返回内容的,这也就说明useState要在最顶部声明,而且次序要固定,不然的话视图显示会得到预期之外的效果。而且useState是不可以在任何有逻辑的地方进行定义的(比如判断),不然的话会报错。useState的个数在每一次渲染时顺序要一致。

  • state的重新赋值会引起组件的重新渲染。
  • 那么 React 怎么知道哪个 state 对应哪个 useState?答案是 React 靠的是 Hook 调用的顺序。这个是为什么hooks写在顶部且不能写在逻辑语句中。

useEffect

useEffect===componentDidMount、componentDidUpdate、componentWillUnmount这三个声明周期,让hooks写法摆脱了类组件中繁琐的生命周期用法。

useEffect是渲染产生的副作用,是在渲染之后执行的。

useMemo

  • useMemo和useEffect有着类似的概念,useMemo则是根据依赖来执行函数体中的逻辑,依赖有变化则会执行,依赖没有变化则不会执行。
  • useMemo是需要一个返回值的,而他的返回值是直接参与渲染的,因此useMemo的调用时机是在渲染中执行的。
  • 而且useMemo会缓存上一次的返回值,如果下一次渲染依赖没有变化,就会使用被缓存的上一次的返回值。
useMemo(()=>{//执行体}) // 每次渲染都执行执行
useMemo(()=>{//执行体},[]) // 只在第一次渲染时执行
useMemo(()=>{//执行体},[依赖]) // 依赖变化则useMemo中函数执行
  

useCallBack

useCallBack是用来优化组件的不必要的渲染的,他其实是useMemo的一个变形,他返回的是一个函数。

  • 当父组件每次渲染都会走一遍所有的函数和变量的生成,所有父组件给子组件传入一个函数时,即使这个函数没有被执行,但子组件也会因为父组件的渲染而渲染。useCallBack就是用来解决这个问题的。
  • 执行时期:渲染中执行。
  • useCallBack是由缓存的,他缓存的是一个函数。
useCallBack(()=>{},[]) // 只会在第一次渲染时构建,适用于useCallBack的回调中不依赖(使用)任何其他的值时
useCallBack(()=>{},[依赖])// 根据依赖的变化构建函数,适用于useCallback的回调中有依赖的值
useCallBack(fn)===useMemo(()=>fn) // useCallBack是useMemo的变形

父组件

onClick函数依赖于double,double变化则重新构建onClick函数,double无变化则会不会重新构建函数,使用缓存的函数。

export default function App() {
  const [count, setCount] = useState(0);
  const double = useMemo(() => {
    return count * 2;
  }, [count === 3]);

  const onClick = useCallback(() => {
    console.log("this is onclick"); // 
  }, [double]);

  return (
    <div>
      count:{count}
      <h1>double:{double}</h1>
      <Demo onClick={onClick} />
      <button
        onClick={() => {
          setCount(count + 1);
        }}
      >
        add
      </button>
    </div>
  );
}

子组件

子组件使用memo保持组件的干净性,从父组件传来的onClick函数发生变化才会重新渲染,否则不渲染。

import React, { memo } from "react";

function Demo(props: any) {
  console.log("demo Render");

  return (
    <div>
      <h1>demo</h1>
    </div>
  );
}

export default memo(Demo);

注意:useCallBack要和memo结合使用才能更好发挥useCallBack的好处,绝绝子了!

useRef

可以获取上一次的状态

自定义hooks

可以实现状态逻辑的复用

自定义 Hook 必须以 “use” 开头吗?

必须如此。这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则

在两个组件中使用相同的 Hook 会共享 state 吗?

不会。自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时(相当于创建了一个新的对象,占据了的新的地址,所以和其他使用该hook的地方是互不相干的),其中的所有 state 和副作用都是完全隔离的。

\

Hooks使用法则

  1. 只在顶层使用hooks,不可以在有逻辑的地方使用hooks,因为hooks可能依赖的是调用的次序,如果在有逻辑的地方使用hooks可能会造成每次的渲染周期的hooks顺序不一致,导致渲染出来的次序发生变化。所有为了保证每次的渲染周期中的hooks顺序一致,只能在顶层使用hooks
  2. 只在自定义hooks和组件中使用hooks

React 是如何把对 Hook 的调用和组件联系起来的?

React 保持对当先渲染中的组件的追踪。多亏了 Hook 规范,我们得知 Hook 只会在 React 组件中被调用(或自定义 Hook —— 同样只会在 React 组件中被调用)。

每个组件内部都有一个「记忆单元格」列表。它们只不过是我们用来存储一些数据的 JavaScript 对象。当你用 useState() 调用一个 Hook 的时候,它会读取当前的单元格(或在首次渲染时将其初始化)(这个单元格就代表当前的这个state),然后把指针移动到下一个。这就是多个 useState() 调用会得到各自独立的本地 state 的原因。

\