如何用 Hooks 来实现 React Class Component 写法?零基础入门_Hooks指南攻略

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

如何用 Hooks 来实现 React Class Component 写法?零基础入门

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

如何用 Hooks 来实现 React Class Component 写法?零基础入门_Hooks指南攻略

本文内容包括如下:

  • 一、在 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 同时实现两个功能,只能通过规范的方式来使用,比如:

{
const inputRef = useRef(null);
const compRef = useRef(null);

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

useImperativeHandle(ref, () => ({
// 导出方法
focusInput: handleFocus,
// 导出当前 dom 节点
compRef: compRef
}));

return (

);
});

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 (

);
}” title=”” data-original-title=”复制”>

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>
  );
}
海计划公众号
(0)
上一篇 2020/03/24 05:38
下一篇 2020/03/24 05:38

您可能感兴趣的内容

  • 谈谈前端工程化是个啥?小白帮助_工程化小白常识

    目前来说,Web业务日益复杂化和多元化,前端开发已经由以WebPage模式为主转变为以WebApp模式为主了。现在随便找个前端项目,都已经不是过去的拼个页面+搞几个jQuery插件就能完成的了。工程复杂了就会产生许多问题,比如:如何进行高效的多人协作?如何保证项目的可维护性?如何提高项目的开发质量?前端工程化就是解决以上问题而生的。前端工程化有四个特点:模块

    Web前端 2020/04/03
  • Array.from() 五个超好用的用途入门攻略_array菜鸟教程

    任何一种编程语言都具有超出基本用法的功能,它得益于成功的设计和试图去解决广泛问题。JavaScript 中有一个这样的函数: Array.from:允许在 JavaScript 集合(如: 数组、类数组对象、或者是字符串、map 、set 等可迭代对象) 上进行有用的转换。在本文中,我将描述5个有用且有趣的 Array.from() 用例。1. 介绍在开始之

    2020/03/24
  • angular怎么做前端页面跳转?小白知识_跳转入门教程

    Angular中每个页面的显示都需要三个要素:页面的代码,控制器和页面的URL;当要在同一个页面上呈现不同的视图时,这就需要配置路由啦;angular.js已经为我们封装了一个独立的路由工具ng-route;ng-route是靠URL来改变显示的视图的。angular实现前端页面跳转的方法如下:1、首先在主页面中,嵌入模板视图:为当前路由把对应的视图模板载入

    2020/03/24
  • Chartify菜鸟指南_简单、轻量级的React.js插件来构建动画可拖动的图表

    Chartify菜鸟指南 官方网址:https://chartify-213721.web.app/ GitHub:https://github.com/kis/chartify …

    2020/03/10
  • Feathers.js小白教程_一个非常高效灵活的,可以从零构建应用的框架

    Feathers.js小白教程 官方网址:https://feathersjs.com GitHub:https://github.com/feathersjs/feathers …

    2020/03/06
  • Js闭包的实现原理和作用菜鸟攻略_闭包使用帮助

    闭包的实现原理和作用1、闭包的概念:指有权访问另一个函数作用域中的变量的函数,一般情况就是在一个函数中包含另一个函数。2、闭包的作用:访问函数内部变量、保持函数在环境中一直存在,不会被垃圾回收机制处理因为函数内部声明 的变量是局部的,只能在函数内部访问到,但是函数外部的变量是对函数内部可见的,这就是作用域链的特点了。子级可以向父级查找变量,逐级查找,找到为止

    2020/03/23
  • CSS使用font-size: 0去除内联元素空白间隙基础教程_空白使用帮助

    我们在编写HTML标签的时候,通常会使用换行,缩进来保证代码的可读性。同时,在编写CSS样式的时候,也会需要把一些元素设置为inline或inline-block。这样一来,有时在页面中会出现意外的空白间隙导致页面视图与本意不符。先来了解一下出现空白间隙的原因吧!因为内联元素默认排版中有空白间隙,以此来把每一个内联元素独立分开,这里的空白间隙也就是空白的文本

    2020/03/26
  • web性能优化的15条实用技巧小白教程_技巧菜鸟教程网

    javascript在浏览器中运行的性能,可以认为是开发者所面临的最严重的可用性问题。这个问题因为javascript的阻塞性而变得复杂,事实上,多数浏览器使用单一进程来处理用户界面和js脚本执行,所以同一时刻只能做一件事。js执行过程耗时越久,浏览器等待响应的时间越长。一.提高加载性能1.IE8,FF,3.5,Safari 4和Chrome都允许并行下载j

    2020/03/24
  • 首页白屏优化实践小白攻略_优化使用帮助

    前言自从前端三大框架React、Vue、Angular面世以来,前端开发逐渐趋向规范化、统一化,大多数时候新建前端项目,首先想到使用的技术一定是三大框架之一,框架给前端开发带来了极大的便利和规范,但是由于这三大框架都是JS驱动,在JS没有解析加载完成之前页面无法展示,会处于长时间的白屏,带来了一定的用户体验问题,接下来本篇文章会介绍本人最近在白屏优化时遇到的

    2020/03/24
  • CSS加载会阻塞DOM树的解析和渲染吗?使用说明_渲染小白攻略

    这里说的是头部引入css的情况,首先,我们都知道:css是由单独的下载线程异步下载的。咱们先分析下css加载会影响什么呢? 一个就是DOM树解析,一个就是构建渲染树【render树】。假设都不影响。这个时候你加载css的时候,很可能会修改下面DOM节点的样式,如果css加载不阻塞render树渲染的话,那么当css加载完之后,render树可能又得重新重绘或

    2020/03/29
  • littlebigdetails基础指南_设计细节动效灵感分享网站

    littlebigdetails基础指南 官方网址:https://littlebigdetails.com/ 简介描述:设计细节动效灵感分享网站 Little Big Detai…

    2020/03/10
  • vue 基础知识入门 webpack 前端性能优化入门知识_性能

    背景对于程序开发者而言,开发一个项目不仅仅注重效率和功能,前端的性能问题也是非常重要的。这直接影响用户的体验,从而间接的也反应该项目质量的好坏。 影响项目性能的原因有很多,如:资源文件的大小,业务的繁杂程度等,所以前端优化的方式也很多。这些东西很零碎,容易被人遗忘。优化一: vue-router路由懒加载懒加载: 也叫延迟加载,即在需要的时候进行加载,随用随

    2020/03/20
  • 使用vconsole进行移动端调试使用指南_调试基础指南

    可使用npm进行安装GitHub地址:https://github.com/Tencent/vC…npm install vconsole在项目的根html文件的标签中引入dist/vconsole.min.js
    // init vC

    2020/03/31
  • 还在埋头干活?给程序员的几个忠告指南教程_程序员教程视频

    开门见山,今天这篇文章是给程序员的几个忠告。好吧,我膨胀了,不是大 V 居然也好意思给别人忠告。即使你不是程序员,看看也有好处。1. 坚持学习,高效的学习从去年下半年开始,大大小小的公司开始接二连三的出现裁员。找工作的好时候已经一去不复返了,不像前几年学个安卓、ios 培训班,出来轻松找个 2 万块钱的工作。现在竞争这么激烈,要通过不断学习,提高自己,才能保

    2020/03/23
  • Typescript内置类型与自定义类型基础教程_类型小白知识

    背景大家用过 Typescript 都清楚,很多时候我们需要提前声明一个类型,再将类型赋予变量。例如在业务中,我们需要渲染一个表格,往往需要定义:interface Row {user: stringemail: stringid: numbervip: boolean// …
    }const tableDatas: Row[] = []
    // …有时

    2020/03/30
  • Flutter页面切换(命名路由)使用指南_路由使用帮助

    命名路由使用基本路由相对简单灵活,适用于应用中页面不多的场景。而在应用中页面比较多的情况下,再使用基本路由,会导致大量的重复代码,此时使用命名路由会非常方便路由命名即给页面起个名字,然后直接通过页面名字即可打开该页面要通过名字来指定打开的页面,必须先给应用程序 MaterialApp 提供一个页面名称映射规则,即路由表 routes路由表实际上是一个 Map

    2020/03/24