简介
这次总结的是 高阶组件 和 context ,这些都是
React
的核心知识,学会了不仅对 设计模式 有一定的接触,还会对 架构思想 更加的理解。
context 了解之后对之后的 Redux 学起来也没那么费劲,而 HOC 会让你书写 React 中的代码更加优雅,抽出重复的逻辑避免过多的重复代码。
高阶组件(HOC)
在多个不同的组件中需要用到相同的功能,通常有
Mixin
和 高阶组件 ,但是由于过多的Mixin
会导致项目维护起来苦难,所以 高阶组件 替代了Mixin
。
高阶组件其实是一个函数,接收一个组件作为参数,返回一个新的包装组件作为返回值,类似于高阶函数。高阶组件和装饰器就是一个模式,因此,高阶组件可以作为 装饰器 来使用,高阶组件 有如下好处:
- 适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有 高阶组件(HOC) 。
- Debug友好,它能够被React组件树显示,所以可以很清楚地知道有多少层,每层做了什么。
<!-- renderContent -->
import React, { Component } from 'react';
import wrapWithContent from './wrapWithContent'; // 引入高阶组件
class RenderContent extends Component {
render() {
console.log(this.props)
return(
<div className="header">
<!-- 输出获取到的参数 -->
<h1 style={{backgroundColor: 'red'}}>{this.props.message}</h1>
</div>
)
}
}
RenderContent = wrapWithContent(RenderContent) // 调用时把一个组件传过去
export default RenderContent
<!-- wrapWithContent -->
import React, { Component } from 'react';
<!-- 声明一个函数并返回一个新组件 -->
const hoc = function(ComponentClass) {
return class HOC extends Component {
constructor(props) {
super(props);
this.state = {
message: '高阶组件'
}
}
render() {
<!-- 传递参数 -->
return <ComponentClass message={this.state.message} />
}
}
}
export default hoc
复制代码
这样就可以在别的组件引用并且使用它,复用性极强。
代码复用的方法、形式有很多种,你可以用类继承来做到代码复用,也可以分离模块的方式,它其实就是设计里面的 装饰者 模式:
import React, { Component } from 'react';
import wrapWithContent from './wrapWithContent';
@wrapWithContent // 使用 decorator(装饰器)
class RenderContent extends Component {
constructor(props) {
super(props)
this.state = {
renderContentState: true
}
}
render() {
console.log(this.props)
return(
<div className="header">
<h1>{this.props.message}</h1>
</div>
)
}
}
复制代码
下面是高阶组件常用的方式:
- 属性代理(Props Proxy): 高阶组件通过
ComponentClass
的props
来进行相关操作- 操作
props
。<!-- 可以对原组件的props进行增删改查,通常是查找和增加,删除和修改的话,需要考虑到不能破坏原组件 --> <!-- HOC --> const hoc = function(ComponentClass) { return class HOC extends Component { constructor(props) { super(props); this.state = { message: '我要在高阶组件传递信息' } } render() { <!-- 这里可以获取原生组件的 props --> console.log(this.props, '老子是高阶组件') <!-- 这里添加 props --> return <ComponentClass message={this.state.message} /> } } } 复制代码
- 通过
refs
访问组件实例。<!-- 可以通过ref回调函数的形式来访问传入组件的实例,进而调用组件相关方法或其他操作 --> <!-- HOC --> const hoc = function (ComponentClass) { return class HOC extends Component { constructor(props) { super(props); this.state = { message: '我要在高阶组件传递信息' } } getRenderContentInstance(wrappedComponentInstance) { <!-- 打印原组件的实例 --> console.log(wrappedComponentInstance) } render() { const props = { ref: this.getRenderContentInstance.bind(this) } <!-- 这里传递时会立即调用 ref ,并且获得原组件的实例 --> return <ComponentClass {...props} /> } } } 复制代码
- 提取
state
。<!-- 你可以通过传入 props 和回调函数把 state 提取出来。 --> <!-- 把 input 变成受控组件,并且把状态抽出来 --> <!-- HOC --> const hoc = function (ComponentClass) { return class HOC extends Component { constructor(props) { super(props); this.state = { name: '' } } onChangeUserName(event) { this.setState({ name: event.target.value }) } render() { const attrbutes = { value: this.state.name, onChange: this.onChangeUserName.bind(this) } return <ComponentClass attrbutes={attrbutes} /> } } } <!-- renderContent --> class RenderContent extends Component { render() { console.log(this.props) return( <div className="header"> <input type="text" {...this.props.attrbutes} /> </div> ) } } 复制代码
- 用其他元素包裹
WrappedComponent
,实现布局等目的。<!-- 为了封装样式、布局等目的,可以将WrappedComponent用组件或元素包裹起来,这样组件里面的内容都会有边框包起来。 --> <!-- HOC --> const hoc = function (ComponentClass) { return class HOC extends Component { constructor(props) { super(props); this.state = { message: '老子是高阶组件' } } render() { return ( <div style={{border: '2px solid green'}}> <ComponentClass message={this.state.message} /> </div> ) } } } 复制代码
- 操作
总结来说 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。新的组件使用传入的组件作为子组件。
高阶组件 的作用是用于代码复用,可以把组件之间可复用的代码、逻辑抽离到高阶组件当中。新的组件和传入的组件通过 props 传递信息。
context
由于第二节总结说的 状态提升 在多层组件嵌套之下,要维护起来非常困难,这时如果有个全局状态所有组件都能直接获取的话,就方便多了。
context
其实像就是组件树上某颗子树的全局变量。
某个组件只要往自己的
context
里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的context
只有它的子组件能够访问,它的父组件是不能访问到的。
import PropTypes from 'prop-types'
<!-- index -->
class Index extends Component {
<!-- 验证 getChildContext 返回的对象 -->
static childContextTypes = {
globalData: PropTypes.object
}
constructor(props) {
super(props)
this.state = {
globalData: {
name: 'jack',
age: 18
}
}
}
<!-- 设置 context -->
getChildContext() {
return {
globalData: this.state.globalData
}
}
render() {
return (
<div className="index">
<Child />
</div>
)
}
}
<!-- child -->
class Child extends Component {
<!-- 子组件验证 context -->
static contextTypes = {
globalData: PropTypes.object
}
render() {
console.log(this.context)
return (
<div className="child"></div>
)
}
}
复制代码
无论是父组件设置
context
, 还是子组件获取context
都需要进行数据验证,这说明context
这玩意不能滥用,但是这个特性对于redux
很重要。
父组件通过
getChildContext
设置context
,用childContextTypes
验证getChildContext
返回的数据,子组件用contextTypes
验证父级设置context
的数据,然后就可以获取到context
。
context
打破了组件和组件之间通过props
传递数据的规范,极大地增强了组件之间的耦合性。而且,就如全局变量一样,context
里面的数据能被随意接触就能被随意修改,每个组件都能够改context
里面的内容会导致程序的运行不可预料。