Take a Look at React Components
React 组件知多少...
#
React 组件的类型#
函数组件函数组件,内部不保留 State,也称为 Stateless Component,没有实例,也没有生命周期方法,是一个纯函数变换,支持定义 defaultProps。 每次都会重新渲染生成新组件,所以更适用于小的 UI 视图组件,如 Button。
#
PureComponentPureComponent 是优化性能的重要方法之一。
举个例子,在普通的类组件里,当父组件重渲染时,会自动触发所有的子组件重渲染,即使在子组件并不需要更新的情况下。
PureComponent 在内部更改了生命周期方法 shouldComponentUpdate(nextProps, nextState) 的逻辑来检查组件是否需要重渲染,只有在检测到 Props 或 State 发生改变时才会触发重渲染。
但是需要注意的是,实际上只是对 Props / State 进行了浅对比(Shallow Equal),所以对于 Array / Object 形式的Props / State 直接修改下标并不能触发重渲染。
Click to expand source code of shallow equal
#
类组件状态逻辑难复用
以往的做法是通过 Props 传递 或 HOC(高阶组件),导致层级冗余
趋向复杂难以维护
类组件中到处都是对状态的访问和处理,导致组件难以拆分成更小的组件
this 指向问题
父组件给子组件传递函数时,必须绑定 this:
- 在构造函数中绑定 this:每次父组件刷新的时候,如果传递给子组件其他的 props 值不变,那么子组件就不会刷新
- 在 render() 函数里面绑定 this:因为 bind 函数会返回一个新函数,所以父组件刷新时,即使父组件传递给子组件其他的 props 值不变,子组件每次都会刷新
- 使用箭头函数:即使两个箭头函数的函数体是一样的,都会生成一个新函数,所以父组件刷新的时候,子组件每次都会刷新
- 使用类的静态属性:原理和第一种方法差不多,比第一种更简洁
#
组件的状态逻辑复用之前说到类组件的状态逻辑难复用,其实是组件设计演进历程里由来已久的问题,主要经过了 Mixin、 HOC、Hooks 的更迭。
#
MixinMixin(混入)是一种通过扩展收集功能的方式,它本质上是将一个对象的属性拷贝到另一个对象上,而且可以拷贝在任意多个对象的任意个方法到一个新对象上去,这是继承所不能实现的。
Mixin 的出现主要就是为了解决代码复用问题。
#
Mixin 的实现方式React也提供了 Mixin 的实现,如果完全不同的组件有相似的功能,可以引入来实现代码复用,当然只有在使用 createClass 来创建 React组件时才可以使用,因为在 React 组件的 es6 写法中它已经被废弃掉了。
#
Mixin 的危害Mixin 可能会相互依赖、耦合,不利于代码维护
不同的 Mixin 中的方法可能会相互冲突
当 Mixin 非常多时,组件还要为其做相关处理,这样会给代码造成滚雪球式的复杂性
React现在已经不再推荐使用 Mixin 来解决代码复用问题,因为 Mixin 带来的危害比他产生的价值还要巨大,并且 React 全面推荐使用高阶组件(High Order Component, HOC)来替代它。
#
HOCHOC 并不是 一种API,HOC 可以看作 React 对装饰模式的一种实现,HOC 就是一个函数,该函数接受一个组件作为参数,并返回一个新的组件(典型的函数式编程思想)。
#
HOC 的实现方式属性代理
函数返回一个我们自己定义的组件,然后在 render 中返回要包裹的组件,这样我们就可以代理所有传入的 props,并且决定如何渲染。
实际上 ,这种方式生成的高阶组件就是原组件的父组件,但是对比原生组件在以下方面有增强:
- 可操作所有传入的 props
- 可操作组件的生命周期
- 可操作组件的 static 方法
- 获取 refs
反向继承
函数返回一个组件,继承原组件,在 render 中调用原组件的 render。
由于继承了原组件,能通过this访问到原组件的 生命周期、props、state、render 等,相比属性代理它能操作更多的属性,对比原生组件有以下增强:
- 可操作所有传入的 props
- 可操作组件的生命周期
- 可操作组件的 static 方法
- 获取 refs
- 可操作 state
- 可以渲染劫持
#
HOC 的使用HOC实际上是一个函数,所以我们将要增强的组件作为参数调用 HOC函数,得到增强后的组件。在实际应用中,一个组件可能会被多个 HOC 增强,像下面这样:
这样的代码可读性差,可以通过 compose 函数封装
还可以借助 ES7为提供的 Decorators 来让我们的写法变的更加优雅:
Decorators 是 ES7的一个提案,还没有被标准化,不过 Babel 转码器已经支持,需要提前配置 babel-plugin-transform-decorators-legacy
#
HOC 的应用双向绑定
表单校验
Redux 的 connect
connect 函数其实就做了一件事,将 mapStateToProps 和 mapDispatchToProps 分别解构后传给原组件,这样我们在原组件内就可以直接用 props 获取 state 以及 dispatch 函数了。
#
HOC 的注意事项静态属性拷贝
当我们应用 HOC去增强另一个组件时,我们实际使用的组件已经不是原组件了,所以我们拿不到原组件的任何静态属性,我们可以在 HOC 的结尾手动拷贝。
传递 Refs
使用高阶组件后,获取到的 ref实际上是最外层的容器组件,而非原组件,但是很多情况下我们需要用到原组件的 ref。React16.3版本提供了一个 forwardRef API来帮助我们进行 refs传递,这样我们在高阶组件上获取的 ref就是原组件的 ref了,而不需要再手动传递。
不要在 render 方法内使用 HOC
每次调用高阶组件生成的都是是一个全新的组件,组件的唯一标识响应的也会改变,如果在 render 方法调用了高阶组件,这会导致组件每次都会被卸载后重新挂载。
不要改变原组件(纯函数)
displayName(方便调试)
#
HooksHooks 是 Reactv16.7.0-alpha 中加入的新特性。Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。
#
官方提供的HooksReact 提供的最常用的几个钩子:
useState() 状态钩子 函数接受 State 变量的初始值作为参数,返回值是一个数组,数组的第一个成员是指向当前状态的变量,第二个成员是用来更新状态的函数。
每次渲染都是独立的闭包。
useEffect() 函数接受两个参数,第一个参数是包含异步操作的函数,第二个参数是一个数组,数组内是 Effect 的依赖项,只要这个数组发生变化,回调函数就会执行。如果第二个参数为空,在组件第一次 render 和之后每次 update 后运行, React保证在 DOM 已经更新完成之后才会运行回调。 useEffect() 用来引入具有副作用的操作(即没有发生在视图转换过程中的逻辑),useEffect() 在全部渲染完成之后执行,最常见的就是向服务器请求数据。以前,放在 componentDidMount 里面的代码,现在可以放在 useEffect()。
useLayoutEffect() useLayoutEffect() 在布局完成之后、绘制之前执行。 尽可能使用标准的 useEffect 以避免阻塞视图更新
useContext() 共享状态钩子 利用 React Context API 创建 AppContext 包裹需要通信的子组件,通过 Provider 注入对象。
useReducer() 函数接受 Reducer 函数和 State 初始值作为参数,返回值是一个数组,数组的第一个成员是指向当前状态,第二个成员是发送 action 的dispatch函数。
由于 Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel)。
#
自定义 hook双向绑定
#
hook 的使用动机使用 hook 的优势:
减少状态逻辑复用的冲突风险
避免 HOC 的地狱式嵌套
使用函数组件代替类组件
能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
副作用的关注点分离
副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。以往这些副作用都是写在类组件生命周期函数中的,而 useEffect 在全部渲染完毕后才会执行,useLayoutEffect 会在浏览器 layout 之后、painting 之前执行。
需要注意的是:
- 只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用
- 只能在 React 函数式组件或自定义 Hook 中使用 Hook,不要在其他 JavaScript 函数中调用