前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >组件设计基础(2)

组件设计基础(2)

作者头像
一粒小麦
发布2019-12-04 12:06:26
5810
发布2019-12-04 12:06:26
举报
文章被收录于专栏:一Li小麦

组件生命周期

早期的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个独立的计数器,然后以竖式的形式累加:

代码语言:javascript
复制
// 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的本质

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

代码语言:javascript
复制
// 子组件响应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');
  }
}

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

代码语言:javascript
复制
// 子组件响应prop
class Child {
  constructor(callback){
    callback();
  }
}

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

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

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

首先写一个EventEmitter:

代码语言:javascript
复制
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被更新时

代码语言:javascript
复制
// 订阅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就是这样一个虫洞。

代码语言:javascript
复制
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有共同的一套方法:

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

代码语言:javascript
复制
// 抽象类
class Parent extends Component {
  constructor(){

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

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

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

高阶组件

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

代码语言:javascript
复制
function Hoc(Com){
  return class Wrap extends Component{
    render(){
      return <Com></Com>
    }
  }
}

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

代码语言:javascript
复制
function Hoc(Com){
  return class Wrap extends Com{
    render(){
      // return ...
    }
  }
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 一Li小麦 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 组件生命周期
    • 挂载
      • 更新
        • 卸载
        • 组件通信
          • props通信
            • 状态与通信
              • props的本质
                • 消息部署:订阅-发布模式
                  • 跨代通信:时空旅行的"虫洞"
                    • 全局状态
                    • 组件设计方法论
                      • 继承
                        • 高阶组件
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档