React是前端组件化开发的开山鼻祖,这种开发方式彻底解决了的前端组件复用的痛点。今天,就来研究一下React组件开发。

前端同学一般都会从Vue入门,因为Vue使用的<template>的组件开发方式让前端人员更容易的平滑过渡。Vue的组件很简单,一般来说,一个.vue文件就是一个组件。就像webpack的模块化开发,一个文件就是一个组件。我们在使用组件时也很容易,通过 ES6 的import导入、注册(components),就可以直接使用。

上面简单说了Vue的组件模式,其实,React的使用方式也差不多,毕竟是借鉴它的组件思想。最明显的差别就是声明方式,React用的是JSX语法,这让React变的很灵活。当然,使用方式的灵活也意味着使用者的脑子也要足够的灵活,从而增加了入门的难度。玩的6的人是真喜欢,未入门的是真不能理解为啥把html标签写到js里面呢。

好了,上面扯了那么多,下面就来一探React组件的究竟...

React组件基础

最开始,让我们先来了解一下React组件是如何定义的和分类的。

组件定义

React组件的声明方式有两种(React.createClass已弃用):函数组件和class组件。它们的区别是什么呢?

如果一个组件只根据props渲染页面,没有内部状态state,我们完全可以用函数组件的形式来实现。也就是说,函数组件只是简单的负责展示数据,它里面没有生命周期函数和state状态,不能做业务逻辑处理。

函数组件(无状态组件):

import React from "react";

function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

class组件:

import React, { Component } from "react";

class Welcome extends React.Component {
constructor(props) {
super(props);
// 设置 initial state
this.state = {
text: props.initialValue || 'placeholder'
};
// ES6 类中函数必须手动绑定
this.handleChange = this.handleChange.bind(this);
}
handleClick(event) {
this.setState({
text: event.target.value
});
}
componentDidMount() {
// do something
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>;
<button onClick={this.handleClick}></button>
</div>
)
}
}

注意:

  1. React自义定的组件均为大写字母开头
  2. Props是只读的,如果要改变状态,需要使用状态提升

组件分类

React的组件分成两种类型:展示组件和容器组件。 将组件分成这两类之后,你会发现它们容易更被重用和理解。通常,展示组件负责展示内容,容器组件负责处理业务逻辑。是不是很像上面的函数组件和class组件的区别。

容器组件和展示组件名词都来自于redux中文文档。

  • 展示组件

    1. 关注页面的展示效果(外观)

    2. 内部可以包含展示组件和容器组件,通常会包含一些自己的DOM标记和样式(style)

    3. 通常允许通过this.props.children方式来包含其他组件

    4. 对应用程序的其他部分没有依赖关系,例如Flux操作或store

    5. 不用关心数据是怎么加载和变动的

    6. 只能通过props的方式接收数据和进行回调(callback)操作

    7. 很少拥有自己的状态,即使有也是用于展示UI状态的

    8. 会被写成函数式组件除非该组件需要自己的状态,生命周期或者做一些性能优化

Example:Page、Header、Sidebar、List、UserInfo...

  • 容器组件

    1. 关注应用的是如何工作的

    2. 内部可以包含容器组件和展示组件,但通常没有任何自己的DOM标记 (除了一些包装用的 div) ,并且从不具有任何样式

    3. 提供数据和行为给其他的展示组件或容器组件

    4. 调用Flux操作并将它们作为回调函数提供给展示组件

    5. 往往是有状态的,因为它们倾向于作为数据源

    6. 通常使用高阶组件生成,例如React Redux的connect(),Relay的createContainer()或Flux Utils的Container.create(),而不是手工编写

    Example:UserPage、StoryContainer、 FollowedUserList...

  • 优点

    1. 更好分离关注点。用这种方式写组件,可以更好理解你的应用程序和 UI
    2. 重用性高,展示组件可以用于多个不同数据源。
    3. 展示组件就是你的调色板,可以把他们放到单独的页面,在不影响应用程序的情况下,让设计师调整UI。
    4. 这迫使您提取诸如侧边栏,页面,上下文菜单等“布局组件”并使用this.props.children,而不是在多个容器组件中复制相同的标记和布局。

现在,我们已经知道了展示组件和容器组件的不同点。容器组件倾向于有状态,展示组件倾向于无状态,这不是硬性规定,因为容器组件和展示组件都可以是有状态的。 通常,展示组件被容器组件包裹的,因为容器组件内可以处理业务逻辑,将处理好的数据传递给展示组件。

当不重要或说很难分清时,不要把分离容器组件和展示组件当做教条, 如果你不确定该组件是容器组件还是展示组件是,就暂时不要分离,写成展示组件吧。

高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。还记得上面说的容器组件吗?高阶函数和容器组件有相似之处,都能将相同的逻辑抽离出来复用,都需要包裹展示组件。

高阶组件来源于JavaScript的高阶函数—— 高阶函数就是接受函数作为输入或者输出的函数。高阶组件仅仅只是是一个接受组件组作输入并返回组件的函数。看上去并没有什么,那么高阶组件能为我们带来什么呢?首先看一下高阶组件是如何实现的,通常情况下,实现高阶组件的方式有以下三种:

属性代理

操作props

const HOC = (WrappedComponent) =>
class WrapperComponent extends Component {
render() {
const newProps = {
name: 'HOC'
}
return <WrappedComponent
{...this.props}
{...newProps}
/>;
}
}

我们看到之前要传递给被包裹组件WrappedComponent的属性首先传递给了高阶组件返回的组件(WrapperComponent),这样我们就获得了props的控制权(这也就是为什么这种方法叫做属性代理)。我们可以按照需要对传入的props进行增加、删除、修改(当然修改带来的风险需要你自己来控制)。

抽象state

​ 属性代理的情况下,我们可以将被包裹组件(WrappedComponent)中的状态提到包裹组件中,一个常见的例子就是实现不受控组件受控的组件的转变。

class WrappedComponent extends Component {
render() {
return <input name="name" {...this.props.name} />;
}
} const HOC = (WrappedComponent) =>
class extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
}; this.onNameChange = this.onNameChange.bind(this);
} onNameChange(event) {
this.setState({
name: event.target.value,
})
} render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onNameChange,
},
}
return <WrappedComponent {...this.props} {...newProps} />;
}
}

上面的例子中通过高阶组件,我们将不受控组件(WrappedComponent)成功的转变为受控组件。

组合方式

const HoC = (WrappedComponent, LoginView) => {
const WrappingComponent = () => {
const {user} = this.props;
if (user) {
return <WrappedComponent {...this.props} />
} else {
return <LoginView {...this.props} />
}
};
return WrappingComponent;
};

上述代码中有两个组件,WrappedComponent 和 LoginView,如果传入的props中存在user,则正常显示的 WrappedComponent 组件,否则显示 LoginView 组件,让用户去登录。HoC 传递的参数可以为多个,传递多个组件定制新组件的行为,例如用户登录状态下显示主页面,未登录显示登录界面;在渲染列表时,传入 List 和 Loading 组件,为新组件添加加载中的行为。

反向继承

反向继承是指返回的组件去继承之前的组件

操作props和state

在某些情况下,我们可能需要为高阶属性传入一些参数,那我们就可以通过柯里化的形式传入参数,例如:

import React, { Component } from 'React';

const HOCFactoryFactory = (...params) => {
// 可以做一些改变 params 的事
return (WrappedComponent) => {
return class HOC extends Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}
}

可以通过下面方式使用:

HOCFactoryFactory(params)(WrappedComponent)

这种方式是不是非常类似于React-Redux库中的connect函数,因为connect也是类似的一种高阶函数。反向继承不同于属性代理的调用顺序,组件的渲染顺序是: 先WrappedComponent再WrapperComponent(执行ComponentDidMount的时间)。而卸载的顺序也是先WrappedComponent再WrapperComponent(执行ComponentWillUnmount的时间)。

总结

写到这里算是结束了,很简略地介绍了React的组件。文章虽短,希望能让你大概了解React组件的一些特征。最后总结一下:

  • React组件的声明方式用两种:函数组件和class组件。函数组件只有render一个返回函数,class组件可以有生命周期和其他业务逻辑处理函数

  • React组件从使用形式上分两种:展示组件和容器组件。基本原则:容器组件负责数据获取,展示组件负责根据props显示信息

  • React高阶组件就是参数为组件,返回值为新组件的函数。这样做的好处就是可以抽离一些相同的业务逻辑便于复用。作用类似Mixin,但避免了Mixin的副作用。

React组件略讲的更多相关文章

  1. 从工程化角度讨论如何快速构建可靠React组件

    前言 React 的开发也已经有2年时间了,先从QQ的家校群,转成做互动直播,主要是花样直播这一块.切换过来的时候,业务非常繁忙,接手过来的业务比较凌乱,也没有任何组件复用可言. 为了提高开发效率,去 ...

  2. 如何快速构建React组件库

    前言 俗话说:"麻雀虽小,五脏俱全",搭建一个组件库,知之非难,行之不易,涉及到的技术方方面面,犹如海面风平浪静,实则暗礁险滩,处处惊险- 目前团队内已经有较为成熟的 Vue 技术 ...

  3. 解析Markdown文件生成React组件文档

    前言 最近做的项目使用了微前端框架single-spa. 对于这类微前端框架而言,通常有个utility应用,也就是公共应用,里面是各个子应用之间可以共用的一些公共组件或者方法. 对于一个团队而言,项 ...

  4. 使用reflux进行react组件之间的通信

    前言 组件之间为什么要通信?因为有依赖. 那么,作为React组件,怎么通信? React官网说, 进行 父-子 通信,可以直接pass props. 进行 子-父 通信,往父组件传给子组件的函数注入 ...

  5. 野心勃勃的React组件生命周期

    当你还在写着Angular指令,过滤器,注入,服务,提供者,视图模版的时候,是不是觉得很烦,好在这个时候,React已经神一样的出现在历史舞台. React组件    React实现了UI=Fn(St ...

  6. 推荐一款中国风React组件

    最近看这个中国风的组件在掘金也火了一段时间,看了有几天了,也体验了下,感觉还不错,所以准备来安利下 项目地址:https://github.com/zhui-team/zhui 使用手册请参考:htt ...

  7. React组件,React和生命周期

    笔记,具体可以看看这个博客: https://segmentfault.com/a/1190000004168886?utm_source=tag-newest react 的jsx document ...

  8. React 组件的生命周期API和事件处理

    一.简单记录React的组件的简洁的生命周期API: A:实例化期: 一个实例第一次被创建时所调用的API与其它后续实例被创建时所调用的API略有不同. 实例第一次被创建时会调用getDefaultP ...

  9. React 16 源码瞎几把解读 【二】 react组件的解析过程

    一.一个真正的react组件编译后长啥样? 我们瞎几把解读了react 虚拟dom对象是怎么生成的,生成了一个什么样的解构.一个react组件不光由若干个这些嵌套的虚拟dom对象组成,还包括各种生命周 ...

随机推荐

  1. Golang的安装和编译

    一.下载安装(Ubuntu16.04) 1.下载地址:https://golang.google.cn/dl/ 2.下载Linux版本的安装包go1.10.3.linux-amd64.tar.gz并复 ...

  2. 数字麦克风PDM信号采集与STM32 I2S接口应用(三)

    本文是数字麦克风笔记文章的数据处理篇. 读取数字麦克风的信号,需要嵌入式驱动和PC应用的结合,驱动负责信号采集,应用代码负责声音分析. 一般而言,在完成特征分析和实验之后,把优化过的代码固化到嵌入式端 ...

  3. (JavaScript) JS方式调用POS机蓝牙打印

    function print(mac_address) { if (!mac_address) { mui.toast('请选择蓝牙打印机'); return; } main = plus.andro ...

  4. Everything可能泄漏大量电脑敏感资料

    一款好用的文件搜索工具Everything被批露出现重大问题: Everything可以打开http服务,在没有加密的情况下任何外网电脑都可以连接的. 因此,Everything可能泄漏大量电脑敏感资 ...

  5. 关于多线程start()方法原理解读

    1.为什么启动线程不用run()方法而是使用start()方法 run()方法只是一个类中的普通方法,调用run方法跟调用普通方法一样 而start()是创建线程等一系列工作,然后自己调用run里面的 ...

  6. GERALD07加强版:lct,主席树,边化点

    Description:N个点M条边的无向图,询问保留图中编号在[l,r]的边的时候图中的联通块个数. 传送门. lct这么神仙的东西一个题解都不写怎么行??? 神仙思路啊. 其实不是很难但是的确不容 ...

  7. 关于css布局的一点记录

    1 关于css实现水平垂直居中的一些方法: .css的定位:用margin,padding,position position:absolute;   //绝对定位,一般父级元素采用relative来 ...

  8. 期末考试(正解:三分单峰函数 me~)

    好久没有水过杂题了! 今天lsc终于刚过了三道考试题来水杂题了! 期末考试 首先一看还是一脸mb(这是正常现象,毕竟我不像一些大神可以一眼出正解)然后我就被颓了标签,知道是三分单峰函数,但是自己实在是 ...

  9. Appium+python自动化(三十九)-Appium自动化测试框架综合实践 - 代码实现(超详解)

    简介 经过一段时间的准备,完善的差不多了,继续分享有关Appium自动化测试框架综合实践.想必小伙伴们有点等不及了吧! driver配置封装 kyb_caps.yaml 配置表 参考代码 platfo ...

  10. lenovo ubuntu18.04 找不到网络适配器

    链接: https://pan.baidu.com/s/1YJl-MfG0tVy9sLx4_otmnA 提取码: smfp https://blog.csdn.net/John_chaos/artic ...