React进阶(八)创建组件

思考一个问题:
在DOM结构中的某一个div元素,是一个对象吗?

如果第一时间无法确定的同学可以在浏览器的Console中创建并查看一个div元素,如下图。

image.png

很显然,所有的DOM元素其实都是一个对象,而每当我们在html中写入一个div元素时,其实就是相当于new了一个div的实例。

<div></div>

// like
new div()

我想很多人其实没有意识到这一点,在React中,也运用了类似的理念。当我们学习React时,理解到这个知识点非常重要,因为在html中,我们不需要创建DOM元素,而在React中则不同,我们通常情况下会创建自己的组件,然后在jsx中实例化,这简直像极了DOM元素的使用。

是不是感觉学到知识了,求赞求赞 ^ ^

那么理解了DOM元素的这个点,并且知道其实React元素其实是非常类似的,那么现在我们思考一下我们会如何创建React组件呢?很显然,React组件实际上就是一个对象,在ES6的语法中,我们使用class语法来创建对象。因此首先大概会如下写:

class ReactDemo {
}

每一个React组件都内含了许多共同的逻辑,例如生命周期,状态等,因此必然会有基类来实现这些共通的逻辑,所以我们会继承他 React.Component

class ReactDemo extends React.Component {

}

我们需要进一步学习的,则就是这些组件共通的特性,他们分别是:

  • 生命周期
  • state
  • props
  • ref

学完了这些知识点,你就可以大胆的宣告React你已经学会啦 ~

生命周期函数 render

生命周期中,最常见的就是render。顾名思义,它就是用来渲染模板语言jsx的。状态<state>与属性<props>每次改动,它都会执行一次,因此它是执行次数最多的生命周期方法,同时通过优化代码逻辑,减少它的执行次数,也是优化React程序性能的最常用手段。因为在无意识中,你的逻辑可能会让render无意义的执行更多次。

因此创建组件的下一步,就是渲染模板语言

class ReactDemo extends React.Component {
  render() {
    return (
      <div>hello world!</div>
    )
  }
}

就这样,我们已经创建了一个最简单的组件。我们已经可以将组件ReactDemo运用起来了。

import { render } from 'react-dom';
import ReactDemo from './ReactDemo';

render(<ReactDemo />, document.querySelector('#root'));
生命周期函数 componentWillMount

当第一次render执行完,则表示组件已经完全创立。
因此如果我们想要在组件创建完成之前对组件做一些事情,那么我们可以使用componentWillMount,该组件的应用场景相对比较少,我通常用它来初始化一些无副作用的数据。

无副作用:不会引发组件render函数的执行

class ReactDemo extends React.Component {
  currentKey = ''
  
  componentWillMount() {
    this.currentKey = this.props.key || 'tony'
  }

  render() {
    return (
      <div>hello {this.currentKey}!</div>
    )
  }
}
生命周期函数 componentDidMount

当组件第一次render执行完,会通过componentDidMount告诉我们。意味着此时组件处于一个相对稳定的状态,我们可以处理更多的逻辑。例如获取网络请求的数据等。

class ReactDemo extends React.Component {
  componentDidMount() {
    axios.get('xxxxx').then(res => {
      // todo
    })
  }

  render() {
    return (
      <div>hello wrold!</div>
    )
  }
}
生命周期函数 componentWillUnmount

当我们切换页面或者需要隐藏掉一些组件时,组件会被销毁,在销毁之前,componentWillUnmount会告诉我们。在这个钩子函数中,我们常常会手动处理一些扫尾工作,例如取消某些事件监听,清除定时器,重置存储在闭包中的引用等。

class ReactDemo extends React.Component {
  componentDidMount() {
    document.addEventListener(...)
  }

  componentWillUnmount() {
    document.removeEventListener(...)
  }

  render() {
    return (
      <div>hello wrold!</div>
    )
  }
}

需要注意的是,willMount, didMount, WillUnmount三个生命周期函数在组件的整个生命周期中仅执行一次。
理解了这三个函数的使用,基本已经可以应对大多数场景。因此生命周期函数暂时先介绍到这里,后续再介绍其他的生命周期函数。

我们想要让一个组件呈现出来的样子与逻辑是可控的,那么就必然会引入状态管理的机制。一个组件通常受控于内部状态state外部状态props

每一个状态的改动,都会引起render函数执行,这一点非常重要。

内部状态state

每个组件实例的内部状态仅仅只需要管理当前实例,因此state必然是挂载在构造函数中,因此我们通常使用如下的方式初始化state

不懂这句的朋友就必须回到<前端基础进阶>中去补充基础知识了,这个点非常重要

class Demo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      bar: true
    }
  }

  render() {
    return (
      <div>{this.state.bar ? 'true' : 'false'}</div>
    )
  }
}

正因为state挂载中构造函数中,知道babel编译结果的同学应该会明白,state的初始化还可以简写成如下方式:

class Demo extends React.Component {
  state = {
    bar: true
  }

  render() {
    return (
      <div>{this.state.bar ? 'true' : 'false'}</div>
    )
  }
}

如上所示,在使用时,我们通过this.state来访问state的值。
除此之外,我们还可以通过this.setState的方式来改变state的值。

class Demo extends React.Component {
  state = {
    data: []
  }

  componentDidMount() {
    axios.get('/group/users').then(res => {
      this.setState({
        data: res.data
      })
    })
  }

  render() {
    const { data } = this.state;
    return (
      <div>
        {data.map((item, key) => (
          <div key={key}>{item.name}</div>
        ))}
      </div>
    )
  }
}

在这个例子中,render函数会执行两次,第一次为组件初始化时,data没有数据。第二次是接口请求成功后,我们手动修改了state的值,引发一次render的修改。

在这里我们可以总结几个非常有意思的点:

  • 当我们想要改变页面时,不再直接修改DOM元素,而仅仅只是修改了state中的数据。这个思维的转变也是React的核心所在,它直接改变了我们前端的开发思维与开发模式
  • render会因为状态的变化而多次执行,而状态的变化不可预测,因此计算量大的逻辑通常不会放在render中来执行,否则会带来更高的性能消耗
  • 当组件状态首次稳定了之后我们才会尝试去修改它,因此通常我们会将数据请求放在componentDidMount中执行
  • setState是一个异步操作。明白事件循环的同学知道了这个点之后坑定能够瞬间明白它可能会带来的麻烦与小坑。所以,如果还不知道事件循环的同学得回过头去补补知识了

大家还可以深入思考一下,为什么setState需要异步的方式,如果就弄成同步的方式会引发什么麻烦?<面试必备>

外部状态props
<div className="foo" index="1" dataset="xxx"></div>

熟练使用html的同学对上面例子中的class, index, dataset肯定不会陌生。当我们用这种方式尝试从外部控制组件的呈现时,就需要用到props。在div组件的内部,我们可以使用如下的方式访问外部传进来的参数。

this.props.className
this.props.index
this.props.dataset

我们可以使用defaultProps来设置props的默认值

例如我们想要创建一个方块,在创建时我们可以随意定制它的颜色,长宽。那么我们可以简单如下实现。

class Rect extends React.Component {
  static defaultProps = {
    color: 'red',
    width: '10px',
    height: '10px'
  }

  render() {
    const { color, width, height } = this.props;

    const _style = {
      color, width, height
    }
    return (
      <div style={_style} />
    )
  }
}

<Rect />
<Rect color="orange" width="20px" height="40px" />

我们不能直接在内部修改props的状态
传入到组件中的props发生变化时,render函数仍然会执行一次

生命周期函数 componentWillReceiveProps

每次接收到新的props之前<组件初始化除外>,componentWillReceiveProps都会执行一次,因此我们可以利用它来判断某些关键的props是否发生了变化。

在该函数中,旧的props使用this.props访问,而新的props会通过参数传入

componentWillReceiveProps(nextProps) {
    if (this.props.bar === nextProps.bar) {
      return;
    }
  }

需要警惕的是,许多人常常在想不到其他办法的情况下会在该函数中根据props来修改内部状态state。这是一个非常危险的行为。特别是当前组件拥有大量子元素时,这种行为会给你的代码性能带来严峻的考验,甚至可能是异常灾难。

// 例如随便摘抄一个例子
componentWillReceiveProps: function(nextProps) {
  this.setState({
    likesIncreasing: nextProps.likeCount > this.props.likeCount
  });
}

我通常只在没有什么子元素的组件中使用这个生命周期函数,其他场景会想办法避开这个雷区。

另外update相关的几个生命周期如果使用不善也会存在性能问题,因此也不建议大家在实践中使用,非要使用时请千万要谨慎

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,743评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,296评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,285评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,485评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,581评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,821评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,960评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,719评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,186评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,516评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,650评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,329评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,936评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,757评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,991评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,370评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,527评论 2 349

推荐阅读更多精彩内容