Genius Lab.

React

Word count: 3.2kReading time: 12 min
2020/03/20 Share

参考资料:

《深入React技术栈》

​ React官方文档

​ React.js小书

《React状态管理与同构实战》

React 工作原理

React把真实DOM树转换成js对象树,也就是虚拟DOM,每次数据更新后重新计算虚拟DOM,并和上一次生成的虚拟DOM做对比,对发生变化的部分做批量更新.

函数式编程:利用函数构建一个规则,返回相应的结果,这个函数可以被多次利用

React通过创建与更新虚拟元素(virtual element)来管理整个虚拟DOM

React中创建的虚拟元素可以分为两类:DOM 元素和组件元素,分别对应着原生 DOM 元素与自定义元素

JSX语法

JSX类似于HTML,可以用来创建虚拟元素(element)

元素标签的属性

DOM 元素的属性 是标准规范属性,但有两个例外——class 和 for

class 属性改为 className;

for 属性改为 htmlFor

自定义 HTML 属性

如果在 JSX 中往 DOM 元素中传入自定义属性,React 是不会渲染的:

1
2
3
4
<div d="xxx">content</div> 
如果要使用 HTML 自定义属性,要使用 data- 前缀,这与 HTML 标准也是一致的:
<div data-attr="xxx">content</div>
然而,在自定义标签中任意的属性都是被支持的:

以 aria- 开头的属性同样可以正常使用:

1
<div aria-hidden={true}></div>

JavaScript 属性表达式

属性值要使用表达式,要用 {}

React组件

分类

  • Function Component 无状态组件

    无生命周期

  • Class Component 有状态组件

    有生命周期

    常用生命周期方法

    render() 是 class 组件中唯一必须实现的方法。

    constructor()

    如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

    通常,在 React 中,构造函数仅用于以下两种情况:

    componentDidUpdate()

    componentDidUpdate() 会在更新后会被立即调用。首次渲染不会执行此方法。

  • UI组件和容器组件
  • 高阶组件 HOC

构建方式

使用方法或类来构建

官方在 React 组件构建上提供的方法:ES6 classes 和无状态 函数(stateless function)。

ES6 classes

1
2
3
4
5
class App extends to Component {
constructor() {
super()
}
}

无状态组件

1
function App() { }

无状态组件只传入 props 和 context 两个参数;也就是说,它不存在 state,也没有生命周 期方法,组件本身即上面两种 React 组件构建方法中的 render 方法。不过,像 propTypes 和 defaultProps 还是可以通过向方法设置静态属性来实现的。

React 的所有组件都继承自顶层类 React.Component。它的定义非常简洁,只是初始化了 React.Component 方法,声明了 props、context、refs 等,并在原型上定义了 setState 和 forceUpdate 方法。内部初始化的生命周期方法与 createClass 方式使用的是同一个方法创建的

组件的组成

属性 props

状态 state

生命周期方法

React 组件可以接收参数,也可能有自身状态。一旦接收到的参数或自身状态有所改变, React 组件就会执行相应的生命周期方法,后渲染。整个过程完全符合传统组件所定义的组件职责

组件的设计

以tabbar为例,activeIndex为当前tab索引

  • activeIndex在内部更新。当我们切换 tab 标签时,可以看作是组件内部的交互行为,被选择后通过回调函数返回具体选择的索引。
  • activeIndex在外部更新。当我们切换 tab 标签时,可以看作是组件外部在传入具体的索引,而组件就像“木偶”一样被操控着。

我们形象地把第一种和第二种视角写成的组件分别称为智能组件(smart component)和木偶组件(dumb component)。

高阶组件

(High Order Component, HOC)实际就是一个高阶函数

函数可以作为参数被传递

1
2
setTimeout(() => (...), 1000)
把函数作为参数,所有它是一个高阶组件

函数可以作为返回值输出

1
2
3
4
5
function foo(x) {
return function() {
return x
}
}

应用

setTimeout, setInterval

ajax

数组中的应用 some() every() filter() map() forEach()

高阶组件基本概念

高阶组件就是接受一个组件作为参数并返回一个新组件的函数

高阶组件是一个函数,并不是组件

如何编写

1 实现一个普通组件

2 将普通组件使用函数包裹

使用高阶组件

1 hoc(warppedComponent)

2 @ hoc

高阶组件只写逻辑,可以用来包裹需要实现相同操作(如:从LocalStorage中加载特定字段)的组件,实现代码复用

React数据流

数据是自顶向下单向流动,即从父组件到子组件。

React中的this

在使用calss方式创建react组件时,它的事件处理函数并不会去主动绑定this,比如我们定义一个handleClick方法,再在这个方法中定义this.setState方法,此时this.setState的this指向类的实例,但是在render函数中调用handleClick方法中,由于找不到this,调用setState方法会报错。我们可以在constructor函数中为handleClick绑定this,或者使用箭头函数

条件渲染

  • 声明一个变量并使用 if 语句进行条件渲染
  • 使用与运算符&&:true && expression 总是会返回 expression, 而 false && expression,react不会渲染这个元素。
  • 三目运算符:condition ? true : false

表单

受控组件 controlled component

每当表单的状态发生变化,都会被写入到组件的state中。

总结下 React 受控组件更新 state 的流程:

(1) 可以通过在初始 state 中设置表单的默认值。

(2) 每当表单的值发生变化时,调用 onChange 事件处理器。

(3) 事件处理器通过合成事件对象 e 拿到改变后的状态,并更新应用的 state。

(4) setState 触发视图的重新渲染,完成表单组件值的更新。

类似于Vue的双向数据绑定

1 状态属性

React 的 form 组件提供了几个重要的属性,用于展示组件的状态。
 value:类型为 text 的 input 组件、textarea 组件以及 select 组件都借助 value prop 来展示 应用的状态。

 checked:类型为 radio 或 checkbox 的组件借助值为 boolean 类型的 selected prop 来展示 应用的状态。

 selected:该属性可作用于 select 组件下面的 option 上,React 并不建议使用这种方式表 示状态,而推荐在 select 组件上使用 value 的方式。

2 事件属性

onChange

处理多个输入

当需要处理多个input元素时,可以给每个元素添加name属性,并让处理函数更加event.target.name的值选择要执行的操作

非受控组件

使用ref提取DOM中的值

Fragments

<></> 可以用来替代div

Context

应用场景

通常应用于存在多个层级的组件中,如果底层组件需要获取数据,可以使用Context直接获取,避免props的层层传递

API

1
const MyContext = React.createContext(defaultValue

创建一个 Context 对象。当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。

只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。

Context.Provider

1
<MyContext.Provider value={  }>

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。

key

react的key是元素的唯一标识,一般是数据的id

setState

接收对象参数

这里还有要注意的是,当你调用 setState 的时候,React.js 并不会马上修改 state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到 state 当中,然后再触发组件更新。

1
2
3
4
5
6
7
8
 handleClickOnLikeButton () {
console.log(this.state.isLiked)
this.setState({
isLiked: !this.state.isLiked
})
console.log(this.state.isLiked)
}
...

你会发现两次打印的都是 false,即使我们中间已经 setState 过一次了。这并不是什么 bug,只是 React.js 的 setState 把你的传进来的状态缓存起来,稍后才会帮你更新到 state 上,所以你获取到的还是原来的 isLiked

所以如果你想在 setState 之后使用新的 state 来做后续运算就做不到了,例如:

1
2
3
4
5
6
7
...
handleClickOnLikeButton () {
this.setState({ count: 0 }) // => this.state.count 还是 undefined
this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
}
...

如果后续操作依赖前一个setState,应该使用函数作为参数

1
2
3
this.setState(
(preState, props) => {}
)

父子组件通信

父 -> 子

props

子 ->

父组件使用子组件的参数和方法

先判断父组件需要什么数据以及如何操作这些数据,然后父组件给子组件传递一个方法,这个方法会保存在子组件的props中,子组件点击(或其它操作)时调用这个方法,并将子组件的数据传入,在父组件中就可以拿到这些数据并进行后续操作

提示:父组件需要什么数据,在子组件进行什么操作时要进行传递,通过on开头的事件监听方法传递数据

父组件需要comment数据,父组件给子组件传递一个on方法,在子组件触发点击事件时,判断props中有无该方法,如果有就调用,并把所需的参数传入

父组件 需要index来删除相应的数据

中间组件 在子组件触发handleDelete方法时传递index

子组件 在点击删除按钮时通过调用父组件的函数传递index值(index值通过props.index获取

handleDelete(index) {

​ comments.splice(index, 1)

}

====================================

handleDelete(index) {

//调用父组件的onDelete方法,把index值传给父组件

​ if(this.props.onDelete) {

​ onDelete(index)

​ }

}

==========================================

handleDelete() {

​ if(this.props.onDelete) {

​ onDelete(this.props.index)

​ }

}

状态提升

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

组件的生命周期

组件的挂载

将组件渲染并且构造DOM元素然后插入页面的过程

1
2
3
4
5
6
7
8
9
constructor()

render()

componentWillMount()

//构造dom元素插入页面

componentDidMount()

组件的更新

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

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

ref属性

ref属性可以用来获取已经挂载的元素的DOM节点

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

ref属性值是一个函数,当input元素在页面上挂载完成后,React.js会调用这个函数,并且把这个挂载以后的DOM节点传给这个函数。在函数中我们把这个DOM元素设置为组件实例的一个属性,这样以后我们就可以通过this.input获取这个DOM元素,并且可以在 componentDidMount 中使用这个 DOM 元素,并且调用 this.input.focus() 的 DOM API。整体就达到了页面加载完成就自动 focus 到输入框的功能。

组件挂载完成后可以调用API,和componentDidMount搭配使用

容器组件

this.props.children 表示组件的所有子节点

this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array

React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object

propTypes 组件参数验证

安装

1
npm install --save prop-types
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//引入模块
import PropTypes from 'prop-types'

static propTypes = {
prop: PropTypes.object
}

PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
CATALOG
  1. 1. React 工作原理
  2. 2. JSX语法
  3. 3. React组件
    1. 3.1. 分类
    2. 3.2. 构建方式
    3. 3.3. 组件的组成
    4. 3.4. 组件的设计
    5. 3.5. 高阶组件
  4. 4. React数据流
  5. 5. React中的this
  6. 6. 条件渲染
  7. 7. 表单
  8. 8. Fragments
  9. 9. Context
  10. 10. key
  11. 11. setState
  12. 12. 父子组件通信
  13. 13. 状态提升
  14. 14. 组件的生命周期
  15. 15. ref属性
  16. 16. 容器组件
  17. 17. propTypes 组件参数验证