高阶组件:
返回值是一个函数的那么一个函数 基本算是个类工厂方法
W (WrappedComponent) 是被包裹的 React.Component;而函数返回的 E (Enhanced Component) 则是新得到的 HOC,也是个 React.Component
使用场景:
- 属性代理:由 HOC 操纵那些被传递给被包裹组件 W 的 props
- 继承反转:HOC 继承被包裹组件 W
hoc能做什么
在大的维度上 HOC 能用于:
- 代码重用和逻辑抽象
- render 劫持
- state 抽象和操纵
- 操纵属性(props)
属性代理
const propsProxy = (props)=>(
WrappedComponent)=>{
return class PP extends React.Component { render() { return <WrappedComponent {...props
}{..this.props}/> } }
}
此处关键的部分在于 HOC 的 render() 方法
返回
了一个被包裹组件的 React Element。同时,将 HOC 接受到的属性传递给了被包裹的组件,因此称为
“属性代理”
。
可以用属性代理做些什么?
- 操纵属性
- 通过 refs 访问实例
- 抽象 state
- 包裹组件
可以对传递给被包裹组件的属性进行增删查改。但删除或编辑重要属性时要谨慎,应合理设置 HOC 的命名空间以免影响被包裹组件。
操纵属性
例子:增加新属性。应用中通过 this.props.user 将可以得到已登录用户
const ppHOC=(currentLoggedInUser)=>(WrappedComponent) =>{ return class PP extends React.Component { render() { const newProps = { user: currentLoggedInUser } return <WrappedComponent {...newProps}/> } }}
通过 refs 访问实例
可以通过
ref
访问到
this
(被包裹组件的实例),但这需要
ref
所引用的被包裹组件运行一次完整的初始化 render 过程,这就意味着要从 HOC 的 render 方法中返回被包裹组件的元素,并让 React 完成其一致性比较过程,而
ref
能引用该组件的实例就好了。
例子:下例中展示了如何通过 refs 访问到被包裹组件的实例方法和实例本身
可用在应用echarts等第三方库时候的场景
function refsHOC(WrappedComponent) { return class RefsHOC extends React.Component { proc(wrappedComponentInstance) { wrappedComponentInstance.method() } render() { const props = Object.assign({}, this.props, {ref: this.proc.bind(this)}) return <WrappedComponent {...props}/> } }}
该高阶组件可以作为一个组件的方法使用
抽象 state
通过提供给被包裹组件的属性和回调,可以抽象 state,这非常类似于 smart 组件是如何处理 dumb 组件的。
例子:在下面这个抽象 state 的例子里我们简单的将 value 和 onChange 处理函数从 name 输入框中抽象出来。
function ppHOC(WrappedComponent) { return class PP extends React.Component { constructor(props) { super(props) this.state = { name: '' } this.onNameChange = this.onNameChange.bind(this) } onNameChange(event) { this.setState({ name: event.target.value }) } render() { const newProps = { name: { value: this.state.name, onChange: this.onNameChange } } return <WrappedComponent {...this.props} {...newProps}/> } }}
用起来可能会是这样的:
@ppHOCclass Example extends React.Component { render() { return <input name="name" {...this.props.name}/> }}
于是这个输入框就自动成为了一个受控组件。
包裹组件
可以利用组件的包裹,实现样式定义、布局或其他目标。一些基础用法可以由普通的父组件完成(参阅附录B),但如前所述,用 HOC 可以更加灵活。
例子:为定义样式而实现的包裹
function ppHOC(WrappedComponent) { return class PP extends React.Component { render() { return ( <div style={{display: 'block'}}> <WrappedComponent {...this.props}/> </div> ) } }}
继承反转
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { return super.render() } }}
被返回的 HOC 类(强化过的类)
继承
了被包裹的组件。之所以被称为“继承反转”是因为,被包裹组件并不去继承强化类,而是被动的让强化类继承。通过这种方式,两个类的关系看起来
反转
了。
继承反转使得 HOC 可以用
this
访问被包裹组件的实例,这意味着
可以访问 state、props、组件生命周期钩子,以及 render 方法
。
可以用继承反转做些什么?
- render 劫持
- 操纵 state
render 劫持
称之为“render 劫持”是因为 HOC 控制了被包裹组件的 render 输出,并能对其做任何事情。
在 render 劫持中可以:
- 在任何 render 输出的 React Elements 上增删查改 props
- 读取并修改 render 输出的 React Elements 树
- 条件性显示元素树
- 出于定制样式的目的包裹元素树(正如属性代理中展示的)
就如我们之前学到的,继承反转 HOC 不保证处理完整的子树,这意味着 render 劫持技术有一些限制。经验法则是,借助于 render 劫持,可以不多不少的操作被包裹组件的 render 方法输出的元素树。如果那个元素数包含了一个函数类型的 React Component,那就无法操作其子组件(被 React 的一致性比较过程延迟到真正渲染到屏幕上时)。
例子1:条件性渲染
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { if (this.props.loggedIn) { return super.render() } else { return null } } }}
例子2:修改 render 输出的元素树
function iiHOC(WrappedComponent) { return class Enhancer extends WrappedComponent { render() { const elementsTree = super.render() let newProps = {}; if (elementsTree && elementsTree.type === 'input') { newProps = {value: 'may the force be with you'} } const props = Object.assign({}, elementsTree.props, newProps) const newElementsTree = React.cloneElement(elementsTree, props, elementsTree.props.children) return newElementsTree } }}
本例中,如果由 render 输出的被包裹组件有一个 input 顶级元素,就改变其 value。
可以在这里做任何事情,可以遍历整个元素树并改变其中的任何一个元素属性。
操纵 state
HOC 可以读取、编辑和删除被包裹组件实例的 state,也可以按需增加更多的 state。要谨记如果把 state 搞乱会很糟糕。大部分 HOC 应该限制读取或增加 state,而后者(译注:增加 state)应该使用命名空间以免和被包裹组件的 state 搞混。
例子:对访问被包裹组件的 props 和 state 的调试
export function IIHOCDEBUGGER(WrappedComponent) { return class II extends WrappedComponent { render() { return ( <div> <h2>HOC Debugger Component</h2> <p>Props</p> <pre>{JSON.stringify(this.props, null, 2)}</pre> <p>State</p><pre>{JSON.stringify(this.state, null, 2)}</pre> {super.render()} </div> ) } }}
该 HOC 将被包裹组件嵌入其他元素中,并显示了其 props 和 state。
使用 HOC 时,就失去了被包裹组件原有的名字,可能会影响开发和调试。
hoc总结:
- render 劫持(在继承反转中看见过)
- 控制内部 props(同样在继承反转中看见过)
- 抽象 state,但存在缺点。将无法在外部访问父元素的 state,除非特意为止创建钩子。这限制了其实用性
- 包裹新的 React Elements。这可能是父组件唯一强于 HOC 的用例,虽然 HOC 也能做到
- 操纵子组件有一些陷阱。比如说如果 children 的根一级并不只有单一的子组件(多于一个的第一级子组件),你就得添加一个额外的元素来收纳所有子元素,这会让你的代码有些冗繁。在 HOC 中一个单一的顶级子组件是被 React/JSX 的约束所保证的。
转自:https://mp.weixin.qq.com/s/dtlrOGTjoneOIiM5kB3XvQ(部分理解稍做修改)