参考资料:
《深入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 | <div d="xxx">content</div> |
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 | class App extends to Component { |
无状态组件
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 | setTimeout(() => (...), 1000) |
函数可以作为返回值输出
1 | function foo(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 | handleClickOnLikeButton () { |
你会发现两次打印的都是 false,即使我们中间已经 setState 过一次了。这并不是什么 bug,只是 React.js 的 setState 把你的传进来的状态缓存起来,稍后才会帮你更新到 state 上,所以你获取到的还是原来的 isLiked。
所以如果你想在 setState 之后使用新的 state 来做后续运算就做不到了,例如:
1 | ... |
如果后续操作依赖前一个setState,应该使用函数作为参数
1 | this.setState( |
父子组件通信
父 -> 子
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 | constructor() |
组件的更新
关于更新阶段的组件生命周期:
shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回false组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。componentWillReceiveProps(nextProps):组件从父组件接收到新的props之前调用。componentWillUpdate():组件开始重新渲染之前调用。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 | //引入模块 |