React Step by Step
react 官方指引反复强调
props与
state
区别。基本上props
类似于其他语言中的形参,state 则相当于内部临时变量。一个是对外沟通的桥梁,而另一个则为交互
私有变量。state
基于与Dom
元素互动考量,这是因为通过setState
方法对state
的变更,会加入异步队列,在合适的时机进行页面渲染。
ES6#
- 不使用 ES6
class
关键字继承React.Component
类- 使用 create-react-class 模块来创建
- 声明默认属性
- 自定义属性对象写到类的 defaultProps 属性
- createReactClass 方法创建组件,那就需要在参数对象中定义 getDefaultProps 方法,并且在这个方法中返回包含自定义属性的对象
- 设置初始状态
- class 关键字创建组件,在 constructor 中给 this.state 赋值
- 使用 createReactClass 方法创建组件,要写一个 getInitialState 方法,并让这个方法返回你要定义的初始属性
自动绑定
- 通过 ES6 class 生成的实例,实例上的方法不会绑定 this
- 使用 class 关键字创建的 React 组件,组件中的方法是不会自动绑定 this
- 因此,需要在 constructor 中为方法手动添加 .bind (this) [建议]
- 使用 createReactClass 方法创建组件,组件中的方法会自动绑定至实例
- 目前还处于实验性阶段的 Babel 插件 Class Properties(注通常意义上的箭头函数属性)
Mixin
混入- ES6 本身是不包含混入支持
- 相似功能 -> 横切关注点
createReactClass
创建 React 组件时,可引入混入- 配置类似
mixins:[SetIntervalMixin]
选项
- 配置类似
- 多个混入定义相同生命周期方法,执行顺序与定义顺序一致
此项织入同 vue 的功能一致,所不同的是在最新版 react,官方推荐使用高阶组件来解决数据共享问题
JSX#
- 不使用 jsx
- 每一个 JSX 元素都只是 React.createElement (component, props, ...children) 的语法糖
- react 组件类型
- 可以是一个字符串
- 亦可以是
React.Component
的子类 - 当组件是无关的时候,它可以是一个普通的函数
React.createElement
-
jsx 语法
class Hello extends React.Component { render() { return <div>Hello {this.props.toWhat}</div>; } } ReactDOM.render( <Hello toWhat="World" />, document.getElementById('root') );
协调(Reconciliation)#
- 目的
- 在单一时间点你可以考虑 render () 函数作为创建 React 元素的树
- React 需要算出如何高效更新 UI 以匹配最新的树,React 基于两点假设
- 两个不同类型的元素将产生不同的树
- 通过过渲染器附带 key 属性,开发者可以示意哪些子元素可能是稳定的
- 对比算法
- 不同类型的元素
- 每当根元素有不同类型,React 将卸载旧树并重新构建新树
- 组件实例会调用
componentWillUnmount() -> componentWillMount() -> componentDidMount()
- 任何与旧树有关的状态都将丢弃
- 相同类型的 DOM 元素
- React 则会观察二者的属性 [即树的状态],保持相同的底层 DOM 节点,并仅更新变化的属性。
- 在处理完 DOM 元素后,React 递归其子元素。
- 相同类型的组件元素
- 当组件更新时,实例仍保持一致,以让状态能够在渲染之间保留
- React 通过更新底层组件实例的 props 来产生新元素
- 并在底层实例上依次调用 componentWillReceiveProps () -> componentWillUpdate () -> render ()
- 递归子节点
- 默认时,React 仅在同一时间点递归两个子节点列表,并在有不同时产生一个变更
- Keys
- React 使用 key 来匹配原本树的子节点和新树的子节点
- key 必须在其兄弟节点中是唯一,但当索引用作 key 时,组件状态在重新排序时会有问题。
- 万不得已,你可以传递他们在数组中的索引作为 key,建议元素在没有重排情况下使用
- 不同类型的元素
- 协调
- 当前实现,子树在其兄弟节点中移动,是无法告知其移动位置
- React 依赖于该启发式算法(即合理假设),若其背后的假设没得到满足,则其性能将会受到影响
- 算法无法尝试匹配不同组件类型的子元素。
- Keys 应该是稳定的,可预测的,且唯一的。
- 非稳定的 key 会使得大量组件实例和 DOM 节点进行不必要的重建,且丢失子组件的状态,性能下降。
Context#
- 应用场景(数据共享)
- React.createContext
- Context 提供了一种在组件之间共享 props 值的方式,而不必通过组件树的每个层级显式地传递 props
- Context 设计目的是为共享那些被认为对于一个组件树而言是 “全局” 的数据,当前认证的用户、主题或首选语言
- React 应用数据是通过 props 属性由上向下(由父及子)的进行传递,使用 context 可以避免通过中间元素传递
- 应用于多个层级的多个组件需要访问相同数据的情景。
- 相关 API
const {Provider, Consumer} = React.createContext(defaultValue);
<Provider value={/* some value */}>
- Comsumer 一个可以订阅 context 变化的 React 组件。
- 父子耦合
- 可以通过 context 向下传递一个函数,以允许 Consumer 更新 contex
Fragment#
- 存在的意义
- html 元素在某些情况下,是不允许添加无意义标签,比如
tr
与td
之间,但React
组件又必须存在一个根节点,Fragment
的出现解决了这一问题 - Fragments 聚合一个子元素列表,并且不在 DOM 中增加额外节点
- 常见模式是为一个组件返回一个子元素列表
- html 元素在某些情况下,是不允许添加无意义标签,比如
- React.Fragment 组件
- <></> 是 的语法糖
- <></> 语法不接受键值或属性
Portals(弹窗对话框)#
- 目的
- Portals 提供一种将子节点渲染到父组件以外的 DOM 节点的方式
ReactDOM.createPortal(child, container)
- 第一个参数(child)是任何可渲染的 React 子元素,如 一个元素,字符串或碎片
- 数(container)则是一个 DOM 元素。
- 应用
- 通常从组件的 render 方法返回一个元素,该元素仅能装配 DOM 节点中离其最近的父元素
- 典型用例
- 当父组件有 overflow: hidden 或 z-index 样式
- 需要子组件能够在视觉上 “跳出(break out)” 其容器,如 对话框、hovercards 以及提示框
- 通过 Portals 进行事件冒泡
- portal 仍存在于 React 树中,而不用考虑其在 DOM 树中的位置
- 一个从 portal 内部会触发的事件会一直冒泡至包含 React 树 的祖先
- 错误边界仅可以捕获其子组件的错误
Web Components#
- React vs web
- Web 组件为可重用组件提供了强大的封装能力
- React 则是提供了保持 DOM 和数据同步的声明式库
- React 组件与 web 组件内都可使用对方
- 由 web 组件触发的事件可能无法通过 React 渲染树来正确冒泡,可能需要手动捕获
高阶组件(hoc)#
- 定义
const EnhancedComponent = higherOrderComponent(WrappedComponent)
- 高阶组件就是一个没有副作用的纯函数,且该函数接受一个组件作为参数,并返回一个新的组件
- 对比组件将 props 属性转变成 UI,高阶组件则是将一个组件转换成另一个新组件
- 应用场景
- 使用高阶组件(HOC)解决交叉问题
- 移除混入,mixins)技术产生的问题要比带来的价值大
- 与混入向组件内部注入,高阶组件更像是变更组件外部生存环境
- 比如 Redux 的 connect 方法和 Relay 的 createContainer.
- 注意点
- 不要改变原始组件,使用组合技术
- 高阶组件就是参数化的容器组件定义
- 容器组件是专注于在高层次和低层次关注点之间进行责任划分的策略的一部分
- 容器组件处理诸如数据订阅和状态管理等事情,并传递 props 属性给展示组件
- 展示组件负责处理渲染 UI 等事情
- 将不相关的 props 属性传递给包裹组件
- 高阶组件给组件添加新特性,不大幅修改原组件的接口(props 属性)
- 最大化使用组合,高阶组件会接收额外的参数
- 高阶组件签名
- 函数签名如下
const ConnectedComment = connect(commentSelector, commentActions)(Comment)
- connect 是一个返回函数的函数(高阶函数)
- React 中组件可以是函数
- 约定俗成
- 包装显示名字以便于调试
- 不要在 render 函数中使用高阶组件
- 必须将静态方法做拷贝
- 理由:当使用高阶组件包装组件,原始组件被容器组件包裹,也就意味着新组件会丢失原始组件的所有静态方法
- Refs 属性不能传递(理由 refs 是一个伪属性,React 对它进行了特殊处理)
Redux#
- 动机
- 不断变化的 state,厘清变化和异步
- state 一个对象树
- 基本数据类型,数组,对象
- 或 Imutable.js 生成的数据结构
- 当 state 变化时需要返回全新的对象,而不是修改传入的参数
- action 普通对象
- action 只是描述了有事情发生什么的普通对象,必须拥有一个 type 域,指示器
- reducer 函数
- 描述 action 如何改变 state 树,是一个纯函数(相同的输入得到相同的输出)
- Redux 所有的 state 都以一个对象树的形式储存在一个单一的 store 中
- 可以用 action 追溯应用的每一次修改,Redux 试图让 state 的变化变得可预测
- 三大原则
- 单一数据源
- 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
- state 是只读的
- 唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
- 使用纯函数来执行修改
- 描述 action 如何改变 state tree ,编写 reducers(纯函数)
- reducer 只是函数,可以控制它被调用的顺序,传入附加数据,或复用 reducer 处理通用任务
- 单一数据源
- 溯源其它
- Redux 规定将模型的更新逻辑集中于一个特定层 (flux 里的 store,Redux 里的 reducer)
- 不允许直接修改数据,而是用普通对象来对变更数据进行描述
- Redux 没有 dispatcher 概念,并假定永不变动数据,通常在 reducer 中返回一个新对象来更新 state
- Elm 函数式编程语言
- Immutable 不可变数据
- 可实现持久数据结构的 JavaScript 库
- 提供了许多永久不可变数据结构
List,Stack,Map,OrderedMap,Set,OrderedSet和Record
- 可变数据(引用类型)
- 可变的好处是节约内存
- 在 JS 中,
objects, arrays,functions, classes, sets, maps
都是可变数据。
- 不可变数据
- js 中数字和字符串皆属于不可变数据,即不会影响之前的数据
- 源于函数式编程,不可变每次操作产生新的对象,新的数据结构(数据拷贝)
- 由于可变在程序越大,代码的可读性,复杂度越来越高(因为其每一变动,可能导致另一变动,会产生副作用)
- 数据拷贝
- 赋值 与 const 定义数据,后者避免了赋值引用
- Object.assign ({},x) 浅拷贝
- deconstruction 解构 浅拷贝(只拷贝一层)
- 深拷贝方式
- 原生
- json 序列化再反序列化
const y = JSON.parse(JSON.stringify(x))
- 第三方库
- lodash
- $.extend(true,...)
- immutable.js 库
- 不可变涉及到数据拷贝算法问题
- 数据持久化
- 数据不可变的时候,当每次操作,都不会引起初始数据的改变
- 对函数编程而言,在一定的时期内,数据是永久存在, 故可通过读取实现类似 "回退 / 切换快照" 操作
- 问题:
- 每次对整个数据结构进行完整的深拷贝,效率会很低
- immutable 部分持久化(即共用没变化的地方)
- 函数编程最重要原则之一不可变数据
- 在使用构造函数创建数组的时候可以省略掉 new 操作符
- js 数组操作方法
- concat () 创建当前数组副本,将接受到的数据添加到个副本末尾
- slice () 返回起止位置之间的数组项(不含结束位置), 此方法不会影响原数组
- splice () 对数组进行删除,插入,替换操作,会改变原始数组
- indexOf () lastIndex () 位置查找
- 迭代方法(数组每一项,当前项的下标,数组对象本身(影响 this))
- every () 皆 true 则返回 true
- filer () 返回为 true 的项
- forEach () 无返回值
- map () 返回函数每次调用结果组成的数组
- some () 有一项为真,则为真
- 归并方法(累计计算)
- reduce()
- reduceRight () 从右项开始累计
Reducer#
- reducer(纯函数,只执行计算)
- 定义 (previousState, action) => newState
- 永远不要在 reducer 里做以下操作
- 修改传入的参数
- 执行有副作用的操作,如 API 请求和路由跳转
- 调用非纯函数,如 Date.now () 或 Math.random ()
- reducer 合成
本作品采用《CC 协议》,转载必须注明作者和本文链接