专栏首页一Li小麦组件设计基础(2)

组件设计基础(2)

组件生命周期

早期的react设计了许多的生命周期钩子。它们严格定义了组件的生命周期,一般说,生命周期可能会经历如下三个过程:

挂载

挂载过程(Mount),也就是把组件第一次在DOM树中渲染的过程;

执行过程如右:constructor(初始化数据,比如设置state和prop的初始值)->componentWillMount(即将废弃)->render(可以认为是个纯函数)->componentDidAmount(ajax请求的主要发生地点,设置state)

constructor:并不是每个组件都需要定义自己的构造函数。在后面的章节我们可以看到,无状态的React组件往往就不需要定义构造函数,一个React组件需要构造函数,往往是为了下面的目的:初始化state,因为组件生命周期中任何函数都可能要访问state,那么整个生命周期中第一个被调用的构造函数自然是初始化state最理想的地方;·绑定成员函数的this环境。

render:render函数无疑是React组件中最重要的函数,一个React组件可以忽略其他所有函数都不实现,但是一定要实现render函数,因为所有React组件的父类React.Component类对除render之外的生命周期函数都有默认实现。

componentDidAmount:需要注意的是,render函数被调用完之后,componentDidMount函数并不是会被立刻调用,componentDidMount被调用的时候,render函数返回的东西已经引发了渲染,组件已经被“装载”到了DOM树上。因为render函数本身并不往DOM树上渲染或者装载内容,它只是返回一个JSX表示的对象,然后由React库来根据返回对象决定如何渲染。而React库肯定是要把所有组件返回的结果综合起来,才能知道该如何产生对应的DOM修改。所以,只有React库调用三个Counter组件的render函数之后,才有可能完成装载,这时候才会依次调用各个组件的componentDidMount函数作为装载过程的收尾。

更新

更新过程(Update),当组件被重新渲染的过程。

当组件被装载到DOM树上之后,用户在网页上可以看到组件的第一印象,但是要提供更好的交互体验,就要让该组件可以随着用户操作改变展现的内容,当props或者state被修改的时候,就会引发组件的更新过程。

组件要更新,有三个触发条件:

1.父组件发生变化

执行过程如右:componentWillReceiveProps(即将废弃)->shouldComponentUpdate(根据true or false决定是否更新)->componentWillUpdate(即将feiqis)->render->componentDidUpdate

2.自身状态变化:通常是state的变化

shouldComponentUpdate->componentWillUpdate->render->componentDidUpdate

3.调用forceUpdate:

shouldComponentUpdate->render->componentDidUpdate

shouldComponentUpdate:除了render函数,shouldComponentUpdate可能是React组件生命周期中最重要的一个函数了。说render函数重要,是因为render函数决定了该渲染什么,而说shouldComponentUpdate函数重要,是因为它决定了一个组件什么时候不需要渲染。render和shouldComponentUpdate函数,也是React生命周期函数中唯二两个要求有返回结果的函数。render函数的返回结果将用于构造DOM对象,而shouldComponentUpdate函数返回一个布尔值,告诉React库这个组件在这次更新过程中是否要继续。在更新过程中,React库首先调用shouldComponentUpdate函数,如果这个函数返回true,那就会继续更新过程,接下来调用render函数;反之,如果得到一个false,那就立刻停止更新过程,也就不会引发后续的渲染了。

说shouldComponentUpdate重要,就是因为只要使用恰当,他就能够大大提高React组件的性能,虽然React的渲染性能已经很不错了,但是,不管渲染有多快,如果发现没必要重新渲染,那就干脆不用渲染好了,速度会更快。我们知道render函数应该是一个纯函数,这个纯函数的逻辑输入就是组件的props和state。所以,shouldComponentUpdate的参数就是接下来的props和state值。如果要定义shouldComponentUpdate,那就根据这两个参数,外加this.props和this.state来判断出是返回true还是返回false。

卸载

卸载过程(Unmount),组件从DOM中删除的过程。

React组件的卸载过程只涉及一个函数componentWillUnmount,当React组件要从DOM树上删除掉之前,对应的componentWillUnmount函数会被调用,所以这个函数适合做一些清理性的工作。比如定时器,自定义时间等。

组件通信

props通信

组件通信一般是通过prop来实现的,

接下来实现一个计算器功能,有3个独立的计数器,然后以竖式的形式累加:

// ClickCounter.js
import React, { Component } from 'react'
import PropTypes from 'prop-types';

const styles = {
    counter: { 
        width: 250, 
        display: 'flex' 
    },
    showbox: {
        width: 80,
        textAlign: 'center'
    },
    label:{
        width:120,
    }
}


class ClickCounter extends Component {

    handleIncrease = () => {
        this.props.handleIncrease();
    }

    handleDecrease = () => {
        this.props.handleDecrease();
    }

    render() {
        return (
            <div style={styles.counter}>
                <div style={styles.label}>{this.props.label}</div>
                <button onClick={this.handleDecrease}>-</button>
                <div style={styles.showbox}>{this.props.count}</div>
                <button onClick={this.handleIncrease}>+</button>
            </div>
        )
    }
}

ClickCounter.propTypes = {
    count: PropTypes.number.isRequired,
    handleIncrease:PropTypes.func.isRequired,
    handleDecrease:PropTypes.func.isRequired
}

export default ClickCounter;

import React, { Component } from 'react';
import ClickCounter from './ClickCounter'
import AllCount from './AllCount';
const styles={
  app:{
    width:250,
    margin:'100px auto'
  }
}

class App extends Component {
  constructor(props) {
    super(props)
    this.state = {
      firstCount: 0,
      secoundCount: 0,
      thirdCount: 0
    }
  }

  handleIncrease=(type)=>{
    this.setState((preState)=>{
      let newState={}
      newState[type]=preState[type]+1;
      return newState;
    });
  }

  handleDecrease=(type)=>{
    this.setState((preState)=>{
      let newState={}
      newState[type]=preState[type]-1;
      return newState;
    });
  }

  getSum=()=>{
    return this.state.firstCount+this.state.secoundCount+this.state.thirdCount
  }

  render() {
    return (
      <div style={styles.app}>
        <ClickCounter 
        label={'first count'} 
        count={this.state.firstCount} 
        handleIncrease={()=>{
          this.handleIncrease('firstCount')
        }}
        handleDecrease={()=>{
          this.handleDecrease('firstCount')
        }}/>
        <ClickCounter 
        label={'secound count'} 
        count={this.state.secoundCount} 
        handleIncrease={()=>{
          this.handleIncrease('secoundCount')
        }}
        handleDecrease={()=>{
          this.handleDecrease('secoundCount')
        }}/>
        <ClickCounter 
        label={'third count'} 
        count={this.state.thirdCount} 
        handleIncrease={()=>{
          this.handleIncrease('thirdCount')
        }}
        handleDecrease={()=>{
          this.handleDecrease('thirdCount')
        }}/>
        <hr />
        <AllCount count={this.getSum()} />
      </div>
    );
  }
}

export default App;

实现效果如下

状态与通信

react的状态怎么放?

三个状态通常是放在顶层组件的,react官方文档《Thinking in React》说:

if you can’t find a component where it makes sense to own the state, create a new component solely for holding the state and add it somewhere in the hierarchy above the common owner component.

•确定每个组件是否依赖于状态?

•找到共同的父级组件(所有需要状态子组件的共同祖先)。

•常见的组件所有者或另一个更高层次结构的组件。注:如果你找不到一个有值得拥有状态的组件,可以创建一个调试用的新组件,让它拥有所有状态,并把它加到常见所有者组件的上层。

长久以来,笔者根据这个守则进行开发。饱受状态过多的困扰。

props的本质

父组件向子组件传值,本质是做了两件事情:初始化实例并调用。

// 子组件响应prop
class Child {
  constructor(name){
    this.$div=document.querySelector('#div');
    this.update(name);
  }
  update(name){
    this.name=name;
    this.$div.innerHTML=name;
  }
}

// 父组件调用
class Parent {
  constructor(){
    this.child=new Child('djtao');
    this.child.update('dangjingtao');
  }
}

子组件向父组件传值,是通过回调函数的形式来实现的

// 子组件响应prop
class Child {
  constructor(callback){
    callback();
  }
}

// 父组件调用
class Parent {
  constructor(){
    this.child=new Child(()=>{
      console.log('设置父组件state!')
    });
  }
}

消息部署:订阅-发布模式

两个不相关的组件A和B怎么通信?

首先写一个EventEmitter:

class EventEmitter{
  constructor(){
    this.eventMap={}
  }
  // 订阅 
  sub(name,callback){
    this.eventMap[name]=this.eventMap[name]||[];
    this.eventMap[name].push(callback);
  }
  // 发布
  pub(name,...data){
    this.eventMap[name]=this.eventMap[name]||[];
    this.eventMap[name].forEach(callback=>callback(...data));
  }
}

当props被更新时

// 订阅update
class A extends EventEmitter {
  construtor(){
    super();
    this.pub('update');
  }
}

class B{
  constructor(){
    this.a=new B();
  }

  // 当子组件的名为update的消息被设置时
  this.child.sub('update',()=>{
      console.log('子组件更新了!');
  })
}

任意两个组件之间通信,可以使用这种方式存放一些全局性的信息。

跨代通信:时空旅行的"虫洞"

从计数器的案例来看,先代和子代通信,只需要不断层层传递props即可。不需要任何新的知识。但这显然不是最佳实践。

如果可以跨代通信,就不必浪费那么多无谓的代码。context就是这样一个虫洞。

import React, { Component } from 'react'
import PropTypes from 'prop-types';

class Child extends Component{
  // 子组件读取context数据
  static contextTypes={
    text:propTypes.string
  }
  constructor(){
  }
    render(){
    return <>{this.context.text}</>
  }
}

class Ancestor extends Component{
  // 先代组件声明context上的text
  static childContextTypes={
    text:propTypes.string
  }
    // 在context上放入数据
    getChildContext(){
    return {
      text:'dangjingtao'
    }
  }
}

这种也不是最好的方案。

全局状态

在前面的探索中,我们已经开始尝试把数据源放在React组件之外形成全局状态。如图所示,让各个组件保持和全局状态的一致,这样更容易控制。

而这个全局状态的最佳实践,就是flux和redux的store。

除了state,利用prop在组件之间传递信息也会遇到问题。设想一下,在一个应用中,包含三级或者三级以上的组件结构,顶层的祖父级组件想要传递一个数据给最低层的子组件,用prop的方式,就只能通过父组件中转。

中间那一层父组件根本用不上这个prop,但是依然需要支持这个prop,扮演好搬运工的角色,只因为子组件用得上,这明显违反了低耦合的设计要求。在flux和redux中我们会探讨如何解决这样的困局。

组件设计方法论

一些指导性原则:

•组件尽可能通过props通信。不用context

•组件属性需要有默认值,做好类型检查

•组件属性尽可能使用简单值。避免使用对象。

•不要给组件限制使用环境:你应该允许它在任意场景下复用。

继承

还记得模板方法模式吗?

假如两个组件A,B有共同的一套方法:

就可以把他们抽象为一个父类:

// 抽象类
class Parent extends Component {
  constructor(){

  }
  getData=()=>{
    //...
  }
}

class A extends Parent{
  // 继承父类的getData
}

class B extends Parent{
  // 继承父类的getData
}

高阶组件

高阶组件本质是一个函数传入一个组件,返回一个新有两种,一种是前文提过的装饰器模式:

function Hoc(Com){
  return class Wrap extends Component{
    render(){
      return <Com></Com>
    }
  }
}

另一种是继承于传入的组件,可用于改写render:

function Hoc(Com){
  return class Wrap extends Com{
    render(){
      // return ...
    }
  }
}

掌握完本章的内容,就可以用react进行开发了。

本文分享自微信公众号 - 一Li小麦(gh_c88159ec1309),作者:一li小麦

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-24

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 基于react的H5音频播放器

    项目是基于React,镶嵌在页面。为此开发了组件audio.js。不过不管什么框架。逻辑都是一样的。

    一粒小麦
  • 组件设计基础(1)

    单一职责原则在react组件设计中依然管用,尤其是维护一个大型的页面时。但也不是事无巨细都需要拆分。根据所谓"高内聚低耦合"的思想,逻辑紧密的组件是不适合拆分的...

    一粒小麦
  • 手撸vuex和vue-router

    把这个store返回出去,那就写完了,核心原理可以说是异常简单。 就用官方文档的案例验证下这个duex有多靠谱:

    一粒小麦
  • react 创建组件以及组件通信

    关于React.createClass方法与class App extends Component方法的区别

    念念不忘
  • 一段探索React自建内部构造的旅程

    在先前的文章里我们涵盖了React基本原理和如何构建更加复杂的交互组件。此篇文章我们将会继续探索React组件的特性,特别是生命周期。

    一个会写诗的程序员
  • React 16 新特性全解(中)

    但是这个事件仅支持那些支持指针事件的浏览器,比如目前最新版本的Chrome,Firefox,Edge IE浏览器)。但是如果你的应用程序真的依赖这些事件,可以使...

    前端迷
  • 微信小程序-音乐播放器+背景播放

    1.正常播放音频 2.可以滑动进度条 3.可以切换上一条,下一条音频 4.退出当前页或关闭小程序之后仍然可以正常播放 5.试听功能进入该播放页不可以播放上一条,...

    super.x
  • 关于蚂蚁庄园登山赛你可以还不知道的秘密!我也写一个康康!Cocos Creator 3D!

    监听TOUCH_MOVE事件控制小鸡左右移动,并要判断边界。注意手指移动的变化和实际屏幕看到的大小是不一样的,这是因为摄像机投影的原因。我是乘了一个系数,可能有...

    白玉无冰
  • ol5里面实现相册地图

    如下图,在手机里面有一个这样的功能,我称之为“相册地图”,本文讲述的是通过扩展ol.style的类,来实现“相册地图”这个功能。

    lzugis
  • React如何处理事件

    以上示例在点击链接时,会报错:Uncaught TypeError: Cannot read property 'setState' of undefined。...

    用户1272076

扫码关注云+社区

领取腾讯云代金券