【D1N910】一线大厂React实践宝典(二) React组件

目录

React 组件

【资料】组件化开发的演变

创建 React 组件

SOLID 原则(基本上都是源自资料)

组件的数据流

组件的生命周期

高阶组件

【资料】装饰者模式


正常操作,正常分析,大家好,我是D1N910。

在大约两年前,我购买了 腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。 

因为我一直基本上是使用Vue来进行前端页面的开发,但是一直没有时间去实践看完。 两年没去看了,真的很惭愧,时间,唉,过得真快啊。

为了在这门课过期之前看完,所以我要抓紧时间学完了。

本系列的专栏记录了我学习 一线大厂React实践宝典  的笔记。 

下面的内容版权归属是  腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。

当然,如果我微不足道的笔记也算是我自己的版权,那么也是有我的小小一份。

你可以用来学习 React。

但是如果你用来商业化盈利,请先获得  腾讯课堂【NEXT】一线大厂 React 实践宝典 制作方的许可。

BTW,作为挖坑狂魔,如果这篇专栏没有更新,那么当我没说过。

这是一个系列的文章,如果你没有看过前面的内容,那么建议你先看一看

这是一个自己的约定,五一,冲冲冲!

今天,拿下 《React 组件》这个战壕,能够到达总体进度,如果一不小心把 《React 与 DOM》学完那就是血赚不亏,虽然不是同一个时间,但是是同一门课程,加油,奥利给,兄弟们,干了!

React 组件

这个课程每节都会有一个很有趣的小剧场,通过抛出一个问题然后接下来进行解答。

这边提了一个场景是,想要实现一个直播录音的功能,这个功能在其他页面已经有实现了,想要直接搬过来使用,但是代码杂糅,不是很容易直接搬过来使用。

通过将我们要用功能抽离为组件就可以比较方便地复用开发了。

 

 

 

组件与组件化

声明组件

概念:组件就像是一个函数。声明了一个属性,就相当于声明了一个组件。

ES6的组件声明的写法:

 

在之前和以后,我们都是会用这种基于 ES6 的 class 的写法。

每一个组件都会通过 render 方法返回一个 React 元素。

 

使用组件

常见的 React 元素有下面两种

1、基于 DOM 标签的 React 元素

const element = <div>Hello react</div>

2、基于自定义组件的 React 元素

const element = <Hello />

 

两者区别:自定义组件首字母大写

 

现在针对我们之前写的 Hello React 进行改造,把 Hello 抽离为组件。

之前的 Hello React

 

在项目的src文件下,新建一个文件夹名称叫 components,这个单词的意思是组件,一般来说,我习惯把项目的组件统一放到 components 里。

然后再在这个 components 文件夹下新建一个 Hello 文件夹,Hello 文件夹下新建 index.jsx,内容如下:

这里我们只需要引入 React,而不需要引入 ReactDom,因为我们只需要在这里制作出一个 React 组件,而不需要把它挂载到 Dom 节点上。

 

 

定义好了以后,我们需要在外部引入,直接 import 即可

 

 

本小节完毕, 仓库

https://github.com/D1N910/learn-react/tree/master/Chapter2/demo-1-hello-components

 

【资料】组件化开发的演变

web1.0 时代

页面代码主要是后端 JSP / PHP 生成的,前端改了功能后,如果想要看到效果,需要代码上传部署后才能够看到,非常影响效率。而且存在后端逻辑混在前端代码里的情况。对于后期来说,非常难以维护。

我本人不幸也算是有幸体验过这个开发流程,确实非常可怕。

后端 MVC 时代

这个时候 Web Server 的架构升级了,有了一些模版引擎,后端同学可以不用在页面模版中揉杂代码,只需要负责给模块返回对应的数据即可,前后端的职责相对明确一些了。但是还是要跑前面的流程。

Ajax 时代

Ajax时代,可以让前端更好地掌握主动权,这段时间比之前舒服多了。但是面临着会维护很多代码的情况。

React.js 带来的组件化时代

React 提供的前端组件化的概念,将很多代码切割好。

 

创建 React 组件

组件的属性

组件的属性-Props

属性的传递

const element = <Hello name="React" />

JSX 属性会被当成一个对象传到组件中。这里可以传入一个数字、字符串、对象、数组、函数、组件等等。

属性的接受

通过访问 this.props 可以获得组件的属性拿到值

Props特性

组件的数据流,Props 是只读的。只能从调用的一方传入,组件自己只能读取不能修改。

举例:

Main 组件 -> 传递 title 属性 -> Header 组件

Main 组件重新 render 的时候,才会传递新的 title 属性给到 Header 组件。

以我们之前的 Hello 组件 传值为例子

 

 

React 还提供了 children 的属性,这个属性能够让组件拿到父级传入的子元素。

改写父文件的 Hello 组件的书写方式,用包括子元素传递。子组件中通过 this.props.children 获取元素。

 

 

对 Props 进行类型检查

为什么需要类型检查?

因为 JavaScript 是一门弱类型的语言,允许变量类型做隐式转换,为了减少错误,我们可以在 React 中引入类型检查模块 —— 这样我们也可以很显而易见地知道我们的组件接受的参数类型是啥。

在组件中导入包 

import PropTypes from 'prop-types';

(别忘了先 npm install 安装一下)

然后对我们的组件添加类型检测方法

 

这里的意思是检测传入的需要是字符串参数。

如果传参有误,会在控制台中抛出报错

 

 

所有能检测的类型在 prop-types 的 npm 包文档说明中

https://www.npmjs.com/package/prop-types

我们也可以通过配置 defaultProps 为 Props 定义默认值

 

函数定义组件

函数定义组件的例子

 

函数定义组件 与 类组件 的区别(内容大部分参考自原文)

1、函数式组件不会被实例化,整体渲染性能得到提升

函数式组件被精简为只有render方法, 没有实例化的过程,不需要分配多余的内容,让性能得到提升。

本身没有this,使用 Ref 等模块(后面会说)时与类组件也会有所区别。

2、函数式组件没有状态

函数式组件本身没有自己的内部状态 state,数据依赖与 props 的传入, 类似刚刚的 

getH1(this.props),所以又称为无状态组件。

3、函数式组件无访问生命周期(后面会说)的方法

函数式组件是不需要组件生命周期管理,所以底层实现这种形式的组件时是不会实现组件的生命周期方法。

4、何时该使用函数式组件

函数式组件相比类组件,拥有更好的性能和更简单的职责,十分适合分割原本庞大的组件,未来 React 也会对函数式组件进行一系列的优化,譬如无意义检查和内存分配领域相关的优化。所以只有有可能,尽量使用函数式组件。

 

本小节完毕,相关代码如下

https://github.com/D1N910/learn-react/tree/master/Chapter2/demo-5-props

 

组件的组合与提取(设计组件)

组件的拆分

举例, 发现 Main 组件是由 组件 A、组件 B 和组件 C 组成的,其中发现组件 C 里面有一个组件 D。

这个自上而下的过程就是组件的 拆分 / 细化。

这个自下而上的过程就是组件的组合。

举例子,下面有一个普通的博客网页

 

提取组件后,结构长下面这样

 

最外层的父文件

 

header 组件

 

section 组件

 

最后查看网页能够正常展示结果~这样我们就完成了一个组件的拆分。

 

本小节结束,对应代码 如下

https://github.com/D1N910/learn-react/tree/master/Chapter2/demo-6-design-components

 

本节对应资料

SOLID 原则(基本上都是源自资料)

SOLID是五个面向对象编程的重要原则的缩写。另外,它也是每个开发者必备的基本知识。了解并应用这些原则能让你写出更优质的代码,变成更优秀的开发者。这个五个原则分别是:

  • SRP(单一责任原则)

  • OCP(开放封闭原则)

  • LSP(里氏替换原则)

  • ISP(接口分离原则)

  • DIP(依赖倒置原则)

单一责任原则(SRP)

* 一个类应该只有一个引起改变的原因

这个原则意味着一个类只应承担一个职责。如果我们的类承担的职责多于一个,那么我们的代码就具有高度的耦合性,任何对它的修改都会可能影响到多个地方,难以维护。遵循该原则可以降低代码的耦合性,提高代码的可维护性。

开闭原则(OCP)

* 软件实体(类,模块,函数等)应该对拓展开放,对修改封闭。

根据这一原则,一个软件实体能很容易地扩展新功能而不必修改现有的代码。这个原则的实现要点在于,使用合理的抽象构建一个实体的框架,功能细节能在实现中扩展。这个原则要求我们在设计软件的时候要预知未来可能发生的改变,合理的抽象实体框架,遵循该原则可以提高代码的可维护性和复用性。

里氏替换原则(LSP)

* 程序里的对象可以在不影响系统正常运作的条件下被它的子类实例所替换

简单来说,这个原则要求我们在继承一个父类的时候,不能修改父类中已经实现的方法。遵循该原则可以提高代码复用性和稳定性,降低系统出现问题的出现几率。

接口隔离原则(ISP)

* 一个类所定义的接口中不应存在该类不需要用到的方法

这个原则要求接口应当被设计成多个专用的接口而不是一个通用接口。不遵循这个原则意味着我们在实现一个接口的时候会依赖很多我们并不需要的方法,但又不得不去定义。遵循该原则可以降低代码的耦合度,后期更易于代码重构。

依赖倒转原则(DIP)

* 高层次的模块不应该依赖于低层次的模块,二者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

这个原则简单来说,就是面向接口变成,它要求一个特定的类不应该直接依赖于另外一个类,但是可以依赖于这个类的抽象(接口)。使用该原则可以降低类与类的耦合度,提高我们代码的复用性。

总结

遵循 SOLID 原则可以帮助我们构建高质量、易扩展、易维护的软件系统,但过分的设计会让简单的问题复杂化,所以我们开发应当是从实际出发,灵活应用 SOLID 原则。

(手打了一遍,希望后面开发组件的时候能够多多用到)

 

 

组件的数据流

小剧场:一个复杂的组件的属性不能都依赖于外部吧,自己内部能够数据管理吗。React 有局部状态属性 State,可以在组件中单独进行维护。

组件的状态 - State

props 和 state 是组件中存储数据的主要方式。

首先来理解下什么是状态。为什么组件会拥有状态。

简而言之,组件就是一个状态机,不同的状态会带给组件不同的表现。

当一个组件拥有不同的状态( state )后,它就会展示不同的样式 (view)。

state -> view

比如我们有一个 Button 组件,它有状态1、状态2和状态3,分别代表着它是黄色、红色和绿色。

State 的声明和使用

下面的代码用 ES6 的写法,在 constructor 这个类的构造函数中,初始化了状态 this.state。

想要获取这个值,和获取props一样,可以使用 this.state.name

 

State 更新

以一个时钟组件为例子。

定一个 clock 属性,初始值是 new Date(),在函数 componentDidMount (马上就会介绍这个函数,现在先说明这是在组件挂载时会执行的方法)定义了一个定时器,每隔1s 去调用 this.setState() 方法更新 state 的 clock 属性,react 检测到了这个变化后,就会执行 render 函数重新去渲染更新视图。

相对于设置了定时器,我们也在 componentWillUnmount(组件将要卸载的时候) 定义了一个清除定时器的方法。

代码和效果如下

 

本小节完毕,相关代码在此

https://github.com/D1N910/learn-react/tree/master/Chapter2/demo-7-state

 

【资料】正确地使用组件局部状态

(又到了无情的打字机器人时间,内容基本上都是照搬自原文的,也有些是我个人的内容)

你使用State的姿势正确了吗?React 的核心思想是组件化的思想,而 State 正是组件中最重要的部分。组件状态的正确使用,可以帮助我们构造出可维护性更佳的组件。

正确地定义组件 State

State 是组件 UI 的数据模型,UI 的改变可以从 State 的变化中反映,而 State 中所有状态的改变都会引起组件 UI 的变化。一个变量是否应该被设置为状态,可以从以下两点进行判断:

  • 这个变量能否通过其他状态或者 props 计算得到?如果可以,则其不应该作为一个状态。

  • 该变量是否在 renader 方法中被使用,如果没有,其不该作为一个状态

简单来说,组件的状态一定要体现到 UI 的更新,且其值是唯一,无法通过计算得到。

不要直接更新 State

以下代码不会重新渲染组件:

// 错误

this.state.comment = 'Hello';

应当使用 setState 方法:

// 正确

this.setState({comment: 'Hello'});

值得一提的是,构造函数是唯一能够初始化 this.state 的地方

状态更新可能是异步的

React 可能会将多个 setState 调用合并为一个调用来提高性能,因此 this.props 和this.state 可能是异步更新的,你不应该靠他们的值来计算下一个状态。

例如,此代码可能无法更新计数器:

// 错误

this.setState({

    counter: this.state.counter + this.props.increment

})

在这种强况下,我们使用 setState 方法时应当接收一个函数而不是一个对象。该函数将旧状态作为第一个参数,将此次更新被应用时的 props 作为第二个参数,如下:

// 正确

this.setState((prevState, props) => ({

    counter: prevState.counter + props.increment

}))

关于 setState 的浅合并

当你调用 setState 方法时,React 将你提供的对象合并到当前状态。
例如我们的组件中包含多个状态:

constructor(props) {

   super(props);

   this.state = {

     clock: new Date(),

      name: 'Da Ben'

   }

 }

我们可以独立的更新其中一个状态属性:

 

componentDidMount() {

     this.setState({

          name: 'Da Ben 2'

    })

 }

这里的合并是浅合并,也就是说 this.setState({name}) 完整保留了 this.state.clock,但完全替换了 this.state.name。

状态提升

在 React 中,状态分享是通过将 state 数据提升至离需要这些数据的组件最近的父组件来完成的。这就是所谓的状态提升。

在 React 应用中,遵循单一数据源原则。简单来说,如果两个或者多个组件依赖于相同的数据,那么这些数据应当放到离需要它们的最近的父组件中,在应用中保持自上而下的数据流。这样的做法可以帮助我们更快的定位 bug,因为组件的状态只有组件自身可以操作,把可能产生 bug 的范围大大缩减了。

实际上就是放到 props 中。

组件的生命周期

小剧场

聪明男:刚刚我们有用到生命周期,那么 React 的生命周期具体是什么呢?

懵懂男:我理解为是一个组件从创建到销毁的过程。

聪明男:你理解得不错。生命周期主要分为三个,分别为组件创建时、组件更新时还有组件销毁时。并且 React 还为我们提供了对应的生命周期函数。

捧哏男:那我们什么时候需要用到这些生命周期函数呢?

聪明男:举个🌰,componentDidMount 代表我们的组件已经挂载到了 DOM上,我们经常会在这个函数里做一些网络请求,还有事件订阅等操作。

懵懂男:这么说,生命周期很重要的吧

聪明男:Yep

组件的生命周期

之前讲了组件的属性 props 和 状态 state。组件的生命周期是第三个非常重要的知识点。

组件的生命周期分为三个阶段,每个阶段有对应的生命周期,不过下面介绍的可能和之前你有接触过的生命周期不太一样,因为这里是 比较新的React 新增了一些生命周期(下面会标绿),又把一些生命周期设置为预备废弃状态。

下面针对的是 v16.4.0 React 生命周期展开详解

(1)装载过程

getDerivedStateFromProps,componentDidMount

(2)更新过程

getDerivedStateFromProps、getSnapshotBeforeUpdate、

shouldComponentUpdate、componentDidUpdate

(3)卸载过程

componentWillUnmount

生命周期的流程

下面请看参考视频的,生命周期流程概览。

 

 

创建时(组件初始化)

 

getDerivedStateFromProps

组件创建之后执行(DOM 未挂载)。

 

componentDidMount

组件完成实例化后执行。

此时我们的组件已经挂载到实际的 dom 上。

这是个很常用的方法。

经常会在这里进行这些操作:

  • 执行 Ajax 请求

  • 执行事件订阅

尝试在之前的时钟方法里使用执行这段创建时,打印出对应的内容

 

 

getDerivedStateFromProps

声明加 static,说明这个函数和组件的状态无关,只要给一个固定的 props 和 固定的 state 就会有固定的产出。

在没有对 state 有进行更新,则返回 null。

但如果我们想对 state 有做修改,比如根据 props 传入的小时来设置 clock 的小时,可以这么做:

props 的属性值 hour 传入 16。

 

 

然后再在 getDerivedStateFromProps 里进行获取设置即可

 

 

组件更新时的生命周期

在 新属性、setState、forceUpdate 的时候会执行组件更新。

 

getDerivedStateFromProps

创建的时候也有,根据属性设置 state,可以获得计算好的新的state

shouldComponentUpdate

在这里可以判断组件是否重新渲染,如果返回 true 则往下执行。否则中断,告诉 react 本次更新不会涉及页面上的渲染,可以节省开销。

getSnapshotBeforeUpdate

这时候dom已经渲染好了,但是我们还没有更新到实际 dom 上,这里我们的返回值可以作为下一生命周期函数 componentDidUpdate 的第三个参数。

有一个快照的作用,记录下我们预挂载但是还没有挂载的状态。

componentDidUpdate

组件完成更新后执行。

 

下面还是用我们之前的钟表组件来尝试执行上面的生命周期,这里我们把更新时间调为 5 秒更新一次,这样能够比较好看得到生命周期。

 

 

组件挂载时

componentWillUnmount

在组件被卸载和销毁之前被调用。我们可以在该方法里做清理工作,例如解绑定时器,取消网络请求,清理任何在 componentDidMount 环节创建的 DOM 元素,取消事件订阅。

可以设定一个定时器让时钟组件挂载,代码如下

 

 

异常处理的周期函数

componentDidCatch

错误边界是一个 React 组件。错误边界组件捕捉发生在子组件树中任意地方的 JavaScript 错误,打印错误日志,并且显示回退的用户界面。错误边界可以捕捉组件渲染期间、生命周期方法中和子组件构造函数中的错误。

如果定义了这一生命周期方法,一个类组件将成为一个错误边界组件。我们可以在错误边界组件捕获到 JavaScript 错误的时候,显示回退的用户界面。

因为可以捕捉到子组件的报错,所以我们一般只需要声明一次错误边界组件,就可以在整个应用程序中使用。

 

 

本小节完毕,相关代码在此

https://github.com/D1N910/learn-react/tree/master/Chapter2/demo-8-lifecycle

高阶组件

什么是高阶组件?高阶组件就是一个函数,它的特点是传入一个组件,然后又返回一个组件。

Fn(组件) = 高阶组件

 

高阶组件的作用:实现了组件逻辑的重用。

举个🌰

A播放器有功能:播放、暂停、上一首,下一首

B播放器有功能:播放、暂停、上一首,下一首

A、B播放器除了展示样式、部分特殊功能以外,大部分功能都是一样的。

可以把 播放、暂停、上一首,下一首 这些播放逻辑抽离出来整合到一个播放器高阶组件内。

用这个播放器高阶组件 分别针对 A 播放器、B 播放器进行整合添加功能,然后输出带有播放器功能的 A 播放器 和 B 播放器,实现一个逻辑的重用。

 

注意

  • 高阶组件不会改变原始组件,它只是在原始组件外面包了一个壳

  • 高阶组件是对原始组件能力的加强

下面针对我们之前的时钟进行一个高阶组件的封装。

为了方便用高阶组件,先把原来的代码改造一下,把 export default 放到下面,实际上效果没差。

 

 

我们做的这个高阶组件的功能,是让组件上面都会有 “你好”

下面把这个方法写好,这个思路挺简单的,感觉也蛮好理解。注释我打满了。

 

使用高阶组件,在我们的 main/index.jsx 中引入 Hello 方法,然后导出 Hello(Main) 即可,这时候我们也能够看到导出的效果有相关的内容。

 

后继也可以在这个高阶组件上加各种通用的方法、组件内容。

本小节完毕,代码如下

https://github.com/D1N910/learn-react/tree/master/Chapter2/demo-9-higher-order-component

 

上面的高阶组件只是图一乐,真要说明白了,还是得看我们的装饰者模式。

【资料】装饰者模式

装饰模式可以在不改变一个对象本身功能的基础上给对象增加额外的新行为。装饰模式就是一种用于替代继承的技术,它通过一种无须定义子类的方式来给对象动态增加职责,使用对象之间的关联关系取代类之间的继承关系。在装饰模式中引入装饰类,在装饰类中既可以调用待装饰的原有类的方法,还可以增加新的方法,以扩充原有类的功能。

例子:

假设有一个老八类,他负责给所有吃的加腐乳

var Laoba = function () {}

Laoba.prototype.work = funciton () {

    console.log('加腐乳');

}

这个时候,大家都希望他在加腐乳的同时,还要加臭豆腐。

在非装饰者模式下,我们可以这样给老八添加“加臭豆腐”的工作

var Laoba = function () {}

Laoba.prototype.work = funciton () {

    console.log('加腐乳');

    console.log('加臭豆腐');

}

这个时候其实是修改了原有类的代码,会导致所有该类的实例都可能发生改变(可能不是同一个时间,但是是同一个地点,不同的老八不用加臭豆腐呢),同时这个也违背了开放-封闭原则。

我们可以这样改动

// 原始的老八类

var Laoba = function() {}

Laoba.prototype.work = function () {

    console.log('加腐乳');

};

// 装饰类

var TicketLaoba = function(laoba) {

    this.laoba = laoba

}

TicketLaoba.prototype.work = function () {

    this.laoba.work();

    console.log('加臭豆腐');

};

var laoba = new TicketLaoba(new Laoba())

laoba.work()

执行无误

我们在一个对象中放入另外一个对象,形成一个聚合对象,被放入的对象(即被装饰的对象)本身没有任何的改变。

使用场景

当我们需要扩展一个类的功能,且使用继承会很复杂的时候,我们可以使用装饰者模式进行功能的扩展。

优点

  • 对于扩展一个对象的功能,装饰模式比继承更加灵活性,不会导致类的个数急剧增加。

  • 可以通过一种动态的方式在运行时选择不同的具体装饰类,从而实现不同的行为。

  • 可以对一个对象进行多次装饰,通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合,得到功能更为强大的对象。

缺点

  • 被装饰多次的对象,报错需要逐级排查,较为繁琐。

  • 使用装饰者模式会产生许多小对象,这些对象的区别在于它们之间相互连接的方式有所不同,而不是它们的类或者属性值有所不同,大量的小对象会占用更多的系统资源,影响程序性能。

     

 

 

Part 2:React组件 学习完毕

总体学习进度(3/9)

一路干到 03:30,妈耶,明天不能摸鱼了

上面的内容都是学习自 腾讯课堂 【NEXT】学院 一线大厂React实践宝典 

个人学习笔记

加油加油加油

蛋糕

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值