举个栗子

好读书,不求甚解

第一阶段

一个组件应该有自己的显示形态行为,组件的显示形态和行为可以由数据状态(state)和配置参数(props)共同决定。数据状态和配置参数的改变都会影响到这个组件的显示形态。

React.js基本环境

React.js基本上无法单独使用,不管是在开发阶段还是生产阶段都需要一堆工具和库辅助。

一般我们说的React.js全家桶:

  • Babel 编译阶段
  • Redux 状态管理工具
  • React-router 编写单页应用必备

使用官方推荐的工具create-react-app创建工程

1
2
3
4
5
6
### 安装create-react-app
npm install -g create-react-app
### 使用create-react-app创建工程
create-react-app hello-react
### 启动工程
cd hello-react && npm start

启动之后会自动打开浏览器看到一个简单的页面了。

JSX

一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素。

JSX 在编译的时候会变成相应的 JavaScript 对象描述。所谓的JSX其实就是JavaScript对象。

从 JSX 到页面到底经过了什么样的过程:
aaa

需要中间这个JavaScript对象结构的原因:

  1. 方便不同的render库渲染到不同的终端。例如:React-canvas渲染到canvas,ReactNative之类。
  2. 当数据变化,需要更新组件的时候,就可以用比较快的算法操作这个 JavaScript 对象,而不用直接操作页面上的 DOM,这样可以尽量少的减少浏览器重排,极大地优化性能。

简单总结一下,要记住几个点:

  1. JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。
  2. React.js 可以用 JSX 来描述你的组件长什么样的。
  3. JSX 在编译的时候会变成相应的 JavaScript 对象描述。
  4. react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。

组件的render方法

在React.js中一切皆组件。在编写React.js组件时,一般要继承React.js的component

一个组件类必须实现一个render方法,这个render方法必须要返回一个 JSX 元素(必须要用一个外层 JSX 元素把所有内容包裹起来)。

在 JSX 中插入 JavaScript 表达式,使用 {} 包裹,{} 中可以放任何 JavaScript 代码,包括变量、表达式计算、函数执行等等。标签和标签的属性上都可以使用表达式。

表达式的值为null时,React.js 会什么都不显示,相当于忽略了该表达式。所以可以使用这个来显示和隐藏某些元素。

组件的state和setState

state 用来存放组件的状态。

setState 方法可以接受一个对象参数来改变 state 中的状态。当我们调用这个函数的时候,React.js 会更新组件的状态 state ,并且重新调用 render 方法,然后再把 render 方法所渲染的最新的内容显示到页面上。

当你调用 setState 时,React.js并不会马上更新 state 中的值,只是将要更新的值缓存起来,稍后才更新。所以当我们 setState 之后,立马使用新的 state 值是无效的。

如何解决这个问题呢?setState 还可以接受一个函数参数。React.js 会把上一个 setState 的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state 的对象:

1
2
3
4
5
6
7
8
9
10
11
12
handleClickOnLikeButton () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
})
// 最后的结果是 this.state.count 为 3
}

这样就可以达到上述的利用上一次 setState 结果进行运算的效果。

组件的props

props 可以让组件具有一定的“可配置”性。每个组件都可以接受一个 props 参数,它是一个对象,包含了所有你对这个组件的配置,一旦传入就无法改变。

props的获取

组件内部通过 this.props 的方式获取组件的参数。

props的传递

在使用一个组件时,可以把参数放在属性标签中,所有的属性都会作为 props 对象的键值。

前面的章节我们说过,JSX 的表达式插入可以在标签属性上使用。所以其实可以把任何类型的数据作为组件的参数,包括字符串、数字、对象、数组、甚至是函数等等。

defaultProps

顾名思义默认参数配置。

总结

  1. 为了使得组件的可定制性更强,在使用组件的时候,可以在标签上加属性来传入配置参数。
  2. 组件可以在内部通过 this.props 获取到配置参数,组件可以根据 props 的不同来确定自己的显示形态,达到可配置的效果。
  3. 可以通过给组件添加类属性 defaultProps 来配置默认参数。
  4. props 一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的 props,从而达到更新的效果。

state vs props

state 的主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能组件被自身控制的数据源。state 中的状态可以被 this.setState 方法进行更,并导致组件重新渲染。

props 的主要作用是让该组件的父组件可以传入参数来配置改组件。它是外部传入进来的配置参数,组件内部无法控制和修改它。因此,除非外部组件传入新的 props,否则组件此时的 props 永远保持不变。

stateprops 有着千丝万缕的关系。它们都可以决定组件的行为和显示形态。一个组件的 state 中的数据可以通过 props 传给子组件,一个组件可以使用外部传入的 props 来初始化自己的 state。但是它们的职责其实非常明晰分明:**state 是让组件控制自己的状态,props 是让外部对组件自己进行配置**。

只要涉及到状态,总是会带来软件设计的复杂性。一个简单规则:尽量少用 state,多用 props。没有 state 的组件叫做无状态组件,React.js 非常鼓励无状态组件。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。

第二阶段

状态提升

当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。

组件的生命周期

挂载阶段

我们把 React.js 将组件的渲染,并构造 DOM 元素并塞入页面的过程称为组件的挂载。React.js 内部对每个组件都有这样的一个过程:

1
2
3
4
5
-> contractor()
-> componentWillMount():组件挂载开始之前,也就是render()之前调用
-> render()
-> componentDidMount():组件挂载完成之后,也就是render()之后调用
-> componentWillUnmount():组件对应的DOM元素在页面删除之前调用

更新阶段

更新阶段的组件生命周期:

  1. shouldComponentUpdate(nextProps, nextState): 你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
  2. componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。
  3. componentWillUpdate():组件开始重新渲染之前调用。
  4. componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。

参考:

React.js中的DOM操作

其实在 React.js 中基本不需要和 DOM 直接打交到。而在 React.js 当中可以直接通过 setState 的方式重新渲染组件,渲染的时候可以把新的 props 传递给子组件,从而达到页面更新的效果。

但是有些时候 React.js 并不能满足所有 DOM 操作需求,我们可以使用 React.js 提供的 ref 属性从 HTML 标签上(其实组件标签也可以)获取一家挂载的 DOM 节点,进而来进行 DOM 操作。

1
2
3
4
5
6
7
8
9
10
11
class AutoFocusInput extends Component {
componentDidMount () {
this.input.focus()
}

render () {
return (
<input ref={(input) => this.input = input} />
)
}
}

但是记住一个原则:能不使用ref就不用。多余的 DOM 操作其实是代码里面的“噪音”,不利于我们理解和维护。

props.children 和容器类组件

容器类组件就像是一个空盒子,我们可以放任意的东西到里面。所有嵌套在这个容器组件中的 JSX 结构都可以在组件内部通过 props.children 获取到:

1
2
3
4
5
6
7
8
9
10
11
class Card extends Component {
render () {
return (
<div className='card'>
<div className='card-content'>
{this.props.children}
</div>
</div>
)
}
}

props.children 获取到的内容打印出来其实是一个数组结构,我们使用起来也会更加的灵活。

使用自定义组件的时候,可以在其中嵌套 JSX 结构。嵌套的结构在组件内部都可以通过 props.children 获取到,这种组件编写方式在编写容器类型的组件当中非常有用。而在实际的 React.js 项目当中,我们几乎每天都需要用这种方式来编写组件。

dangerouslySetInnerHTML 和 style 属性

dangerouslySetInnerHTML

出于安全的考虑(XSS攻击),在 React.js 中所有的表达式插入的内容都会被自动转义,就相当于 jQurey 里面的 text(...) 函数一样,任何 HTML 格式都会被转移掉。为此,React.js提供了一个属性dangerouslySetInnerHTML,可以让我们动态的设置元素的 innerHTML:

1
2
3
4
5
6
7
8
9
...
render () {
return (
<div
className='editor-wrapper'
dangerouslySetInnerHTML={{__html: this.state.content}} />
)
}
...

要注意这个__html 属性值就相当于元素的 innerHTML,这样我们就可以动态的渲染元素的innerHTML结构了。

这里或许有些朋友会觉得很奇怪,为什么 React.js 要把这个搞的这么复杂,其实这个是 React.js团队有意为之,React.js 团队认为把事情搞复杂可以防止(警示)大家滥用这个属性。

style

React.js 中的元素style属性用法和 DOM 中的不太一样:

  • 需要把 CSS 属性变成一个对象再传给元素

    1
    <h1 style={{fontSize: '12px', color: 'red'}}>React.js 小书</h1>`
  • CSS 属性对象里面的 key 值必须是对应的驼峰命名, font-size换成fontSizetext-align换成textAlign

使用对象作为style方便我们使用props或者state中的数据结合setState来修改样式,非常灵活:

1
<h1 style={{fontSize: '12px', color: this.state.color}}>React.js 小书</h1>

然后只要简单的setState({color: 'blue'})就可以很方便的修改元素的颜色为蓝色。

PropsTypes 和组件参数验证

React.js 提供的 PropTypes 提供了一系列的数据类型可以用来配置组件的参数:

1
2
3
4
5
6
7
8
9
PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
...

首先要引入这个库 import PropsType from 'prop-types',然后在定义组件类时设置一个静态方法对象propTypes既可以完成 props 参数类型验证了,很方便吧!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Comment extends Component {
static propTypes = {
comment: PropTypes.object
}

render () {
const { comment } = this.props
return (
...
)
}
}

通过 PropTypes 给组件的参数做类型限制,可以在帮助我们迅速定位错误,这在构建大型应用程序的时候特别有用;另外,给组件加上propTypes,也让组件的开发、使用更加规范清晰。

这里建议大家写组件的时候尽量都写propTypes,有时候有点麻烦,但是是值得的。

待续 Reduce…

本文作者 : Kevalin
本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
本文链接 : https://kevalin.github.io/2019/11/04/React-js%E5%B0%8F%E4%B9%A6%E7%AC%94%E8%AE%B0/