如何用 Hooks 来实现 React Class Component 写法?

更新日期: 2019-09-15阅读: 2k标签: Hooks

Hooks 的 api 可以参照 react 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的,本文 Demo 基于 React 实现 查看 Demo 完整版

本文内容包括如下:

  • 一、在 Hooks 中如何实现 Class Component 生命周期
  • 二、在 Hooks 中如何实现 shouldComponentUpdate
  • 三、在 Hooks 中如何实现 this
  • 四、在 Hooks 中如何获取上一次值
  • 五、在 Hooks 中如何实现父组件调用子组件方法
  • 六、在 Hooks 中如何获取父组件获取子组件的 dom 节点


一、在 Hooks 中如何实现 Class Component 生命周期

Hooks 的出现其实在弱化生命周期的概念,官网也讲解了原先的生命周期在写法上有哪些弊端,这里不做优缺点的比较,只给大家做写法转换。

Hooks 生命周期主要是借助 useEffect 和 useState 来实现,请看如下 Demo

constructor

Class Component constructor 函数只会在组件实例化时调用一次,而且会在所有生命周期函数调用之前调用

useState 传入初始化函数 fn 只会执行一次,并且执行时机在 render 之前

function useConstruct(fn) {
  useState(fn);
}

componentDidMount

依赖项给空数组,只会执行一次

// componentDidMount
function useDidMount(fn) {
  useEffect(fn, []);
}

componentDidUpdate

依赖项不传值,任何触发的 render 都会执行

// componentDidUpdate
function useDidUpdate(fn) {
  useEffect(fn);
}

componentWillUnmount

// componentWillUnmount
function useUnMount(fn) {
  useEffect(() => fn, []);
}

生命周期详细 Demo 如下

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

// construct
function useConstruct(fn) {
  // useState 传入初始化函数 fn 只会执行一次
  useState(fn);
}

// componentDidMount
function useDidMount(fn) {
  // 依赖项给空数组,只会执行一次
  useEffect(fn, []);
}

// componentDidUpdate
function useDidUpdate(fn) {
  // 依赖项不传值,任何触发的 render 都会执行
  useEffect(fn);
}

// componentWillUnmount
function useUnMount(fn) {
  useEffect(() => fn, []);
}

function Block() {
  const [count, setCount] = useState(0);
  const instance = useRef({});

  useConstruct(() => {
    instance.current.name = 1;
    console.log('Block Component----Construct');
  });

  useDidMount(() => {
    console.log('Block Component----componentDidMount');
  });

  useDidUpdate(() => {
    console.log('instance.current.name', instance.current.name);
    console.log('Block Component----componentDidUpdate');
  });

  useUnMount(() => {
    console.log('Block Component----componentWillUnmount');
  });

  console.log('Block render');
  return (
    <div style={{backgroundColor: '#eaeaea'}}>
      <p>Block组件</p>
      <p>count: {count}</p>
      <br />
      <button onClick={() => setCount(count + 1)}>点击 count + 1</button>
    </div>
  );
}

export default function ParentComp() {
  const [unMountBlock, setUnMountBlock] = useState(false);
  return (
    <div>
      <p>unMountBlock: {unMountBlock?'true':'false'}</p>
      <br />
      {!unMountBlock ? <Block /> : null}
      <br />
      <button onClick={() => setUnMountBlock(true)}>点击卸载 Block 组件</button>
    </div>
  );
}


二、在 Hooks 中如何实现 shouldComponentUpdate

通过 useMemo 来实现 shouldComponentUpdate,依赖项填写当前组件依赖的 props,useMemo内部对依赖项进行浅比较,其中的任何一个依赖项变化时,重新 render 组件。 与 Class Component 不同的是,比较操作在组件外部。

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

function Counter(props) {
  console.log('Counter render');
  return (
    <div>
      <p>count: {props.count}</p>
    </div>
  );
}

function Time(props) {
  console.log('Time render');
  return (
    <div>
      <p>time: {props.time}</p>
    </div>
  );
}

export default function Demo() {
  const [count, setCount] = useState(0);
  const [time, setTime] = useState(0);
  const [count2, setCount2] = useState(10);

  // 用于实现 shouldComponentUpdate
  // 与 Class Component 不同点:当前是在组件外做比较
  const child1 = useMemo(() => <Counter count={count} />, [count]);
  const child2 = useMemo(() => <Time time={time} />, [time]);

  return (
    <div>
      <p>count: {count}</p>
      <p>time: {time}</p>
      <p>count2: {count2}</p>
      <br />
      <button onClick={() => setCount(count + 1)}>count + 1</button>
      <br />
      <button onClick={() => setCount2(count2 + 1)}>count2 + 1</button>
      <br />
      <button onClick={() => setTime(time + 1)}>time + 1</button>
      <br />
      {child1}
      {child2}
    </div>
  );
}


三、在 Hooks 中如何实现 this

首先你要明白 Hooks 实际上仍然是 Function Component 类型,它是没有类似于 Class Component 的 this 实例的。

通过使用 useRef 来模拟实现,internalRef.current 可以认为是当前的 this 变量,用来绑定相关变量

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

export default function useThis() {
  // internalRef.current 默认值为 {}
  const internalRef = useRef({});
  // internalRef.current 类似于 this 变量
  const self = internalRef.current;

  if (self.didMount) {
    console.log('componentDidMount', self.didMount);
  }

  useEffect(() => {
    self.didMount = true;
  }, []);

  return (
    <div>
      <p>如何使用this 变量</p>
    </div>
  );
}


四、在 Hooks 中如何获取上一次值

借助 useEffect 和 useRef 的能力来保存上一次值

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

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

export default function Counter() {
  const [count, setCount] = useState(0);
  const previousCount = usePrevious(count);

  return (
    <div>
      <p>count: {count}</p>
      <p>previousCount: {previousCount}</p>
      <button onClick={() => setCount(count + 1)}>点击 count + 1</button>
    </div>
  );
}


五、在 Hooks 中如何实现父组件调用子组件方法

上节已经说到,Hooks 实际上仍然是 Function Component 类型,它本身是不能通过使用 ref 来获取组件实例的,所以在 Hooks 中想要实现 父组件调用子组件的方法,需要两个 API来配合使用,即forwardRef和useImperativeHandle。在子组件中使用 useImperativeHandle 来导出方法,并使用 forwardRef 包裹组件, 在父组件中使用 useRef传递 ref 给子组件。

import React, { useRef, useImperativeHandle, forwardRef } from 'react';

const TextInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus();
  };

  // 暴露方法给外部组件调用
  // useImperativeHandle 应当与 forwardRef 一起使用
  useImperativeHandle(ref, () => ({
    focusInput: handleFocus,
    hello: ()=>{}
  }));

  return (
    <div>
      <input ref={inputRef} type="text" />
      <br />
      <button onClick={handleFocus}>内部调用 Focus the input</button>
    </div>
  );
});

export default function Parent() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    console.log(typeof findDOMNode)
    console.log(inputRef.current)
    // 调用子组件方法
    inputRef.current.focusInput();
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <br />
      <button onClick={handleFocus}>父组件调用子组件focusInput</button>
    </div>
  );
}


六、在 Hooks 中如何获取父组件获取子组件的 dom 节点

findDOMNode 用于找到组件的dom节点,用于相关的 dom 处理操作。

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
import {findDOMNode} from 'react-dom';

const TextInput = forwardRef((props, ref) => {
  return (
    <div ref={ref}>
      <input ref={inputRef} type="text" />
      <br />
      <button onClick={handleFocus}>内部调用 Focus the input</button>
    </div>
  );
});

export default function Parent() {
  const inputRef = useRef(null);

  const handleFindDom = () => {
    const node = findDOMNode(inputRef.current);
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <br />
      <button onClick={handleFocus}>父组件调用子组件focusInput</button>
    </div>
  );
}

这里可能有人会提出疑问,在 Class Component 里面 ref 可以取到组件 dom 的同时,也可以取到组件实例方法,为何这里要拆分成 三、四 两个章节来讲?
很遗憾,在 Hooks 里面无法通过一个 ref 同时实现两个功能,只能通过规范的方式来使用,比如:

import React, { useRef, useImperativeHandle, forwardRef } from 'react';
import {findDOMNode} from 'react-dom';

const TextInput = forwardRef((props, ref) => {
  const inputRef = useRef(null);
  const compRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus();
  };

  useImperativeHandle(ref, () => ({
    // 导出方法
    focusInput: handleFocus,
    // 导出当前 dom 节点
    compRef: compRef
  }));
  
  return (
    <div ref={compRef}>
      <input ref={inputRef} type="text" />
      <br />
      <button onClick={handleFocus}>内部调用 Focus the input</button>
    </div>
  );
});

export default function Parent() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    // 获取 TextInput 组件的 dom 节点
    const node = findDOMNode(inputRef.current.compRef.current);
    console.log(node);
    // 调用 TextInput 组件方法
    inputRef.current.focusInput();
  };

  return (
    <div>
      <TextInput ref={inputRef} />
      <br />
      <button onClick={handleFocus}>父组件调用子组件focusInput</button>
    </div>
  );
}

链接: https://www.fly63.com/article/detial/5930

React将引入Hooks,你怎么看?

近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?

精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。

使用react hooks实现自己的context-redux

我们将userReducer函数返回的原始dispath命名为origin_dispatch,自定义dispatch函数,当action为函数的时候,我们执行action函数,并将origin_dispatch当作参数传进去;action不是函数,直接调用origin_dispatch,不做处理

useEffect Hook 是如何工作的?

使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。

React Hooks 你真的用对了吗?

从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考

React-Hooks

以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。

React Hooks与setInterval

Hooks出来已经有段时间了,相信大家都用过段时间了,有没有小伙伴们遇到坑呢,我这边就有个 setInterval 的坑,和小伙伴们分享下解决方案。写个 count 每秒自增的定时器,如下写法结果,界面上 count 为 1 ?

React Hooks 底层解析[译]

对于 React 16.7 中新的 hooks 系统在社区中引起的骚动,我们都有所耳闻了。人们纷纷动手尝试,并为之兴奋不已。一想到 hooks 时它们似乎是某种魔法,React 以某种甚至不用暴露其实例

React Hooks实践

9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。

用React hooks实现TDD

由于篇幅所限文章中并没有给出demo的所有代码,大家如果有兴趣可以将代码clone到本地从commit来看整个demo的TDD过程,配合文章来看会比较清晰,从进公司前认识了TDD,到实践TDD

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!