一句话介绍HOC
何为高阶组件(HOC),根据官方文档的解释:“高阶组件是react中复用组件逻辑的一项高级技术。它不属于react API的组成部分,它是从react自身组合性质中抽离出来的一种模式。具体来说,高阶组件是函数,它接受一个组件作为参数,然后返回一个新的组件
使用场景
将几个功能相似的组件里面的方法和react特性(如生命周期里面的副作用)提取到HOC中,然后向HOC传入需要封装的组件。最后将公用的方法传给组件。
优势
使代码简洁优雅、代码量更少
HOC(高阶组件)
/* HOC(高阶组件): 接收一个组件,返回包装后的组件(增强组件) - 不是React API - 是一种设计模式,类似于装饰器模式 - ≈ Mixin && > Minxin const 包装后的组件 = 高阶组件(被包装的组件); // e.g. const Wrapper = withRouter(NavBar); 高阶组件会把所有接收到的props,传递给被包装的组件(透传) ref 和 key 类似,不是一个prop,所以不会透传,ref会绑定到外层的包装容器上 | 解决方法可以参考下面的 <<处理ref>> * */
怎样包装组件?
/* 怎样包装组件? 第一种: 普通包装 export时就包装 import React from 'react'; import Hoc from './Hoc'; class Header extends React.Component { render() { return <span>{ this.props.count }</span> } }; export default Hoc(Header); ========== import后再包装: import Header from './header'; import Hoc from './Hoc'; const EnhanceHeader = Hoc(Header); const Home = () => { return ( <div> <EnhanceHeader count={1} /> </div> ) } 第二种: 装饰器包装,只能在类组件中使用 import React from 'react'; import Hoc from './Hoc'; @Hoc export default class Header extends React.Component { render() { return <span>{ this.props.count }</span> } }; ======= @Hoc class Header extends React.Component { render() { return <span>{ this.props.count }</span> } }; export default Header; * */
定义一个简单的HOC
/* 定义一个简单的HOC,接收一个组件,返回一个组件 import React from 'react'; // 返回类组件 export default function Hoc(WrappedComponent) { /* return class extends React.Component {} - 在 React Developer Tools 中展示的名字是 Component return class Wrapper extends React.Component {} - 在 React Developer Tools 中展示的名字是 Wrapper *\ return class extends React.Component { render() { return <WrappedComponent {...this.props} />; } }; } // 返回函数式组件 export default function Hoc(WrappedComponent) { /* return function(props) {} - 在 React Developer Tools 中展示的名字是 Anonymous return function Wrapper(props) {} - 在 React Developer Tools 中展示的名字是 Wrapper *\ return function Wrapper(props) { return <WrappedComponent {...props} />; }; } * */
给Hoc传参
/* 给Hoc传参 // Hoc,可以接受任意参数 export default function Hoc(WrappedComponent, title, user, data) { return class Wrapper extends React.Component { render() { return <WrappedComponent {...this.props} /> } }; }; // 包装时传参 const EnhanceHeader = Hoc(Header, 'title', { name: '霖'}, [1, 2, 3]); * */
Hoc嵌套
/* Hoc嵌套,函数柯里化的原理 // Hoc1: 给组件添加title属性 export default function Hoc1(WrappedComponent, title) { return class extends React.Component { render() { return <WrappedComponent title={title} {...this.props} /> } }; }; // Hoc2: 修改组件的显示内容 export default function Hoc2(WrappedComponent, content) { return class extends WrappedComponent { // 这里用了反向继承 render() { const elementTree = super.render(); // React用Js对象来模拟Dom树结构,可以通过修改Js对象的属性来操纵数据 console.log(elementTree); // 不太了解里面的结构可以打印出来 + 官网cloneElement() 了解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的内容已被劫持: ${content}` }); return newElementTree; } }; }; // 被包裹的组件 export default class Header extends React.Component { render() { const { title } = this.props; return ( <span title={title}> 默认内容 </span> ) } }; // 使用 import Hoc1 from './Hoc1'; import Hoc2 from './Hoc2'; /* 包装过程 1. const Wrapper = Hoc2(Header, '内容'); 2. Hoc1(Wrapper) ** const EnhanceHeader = Hoc1(Hoc2(Header, '内容'), '标题'); export default function Home() { return ( <div> <EnhanceHeader /> </div> ); }; * */
处理ref
/* 处理ref e.g. Hoc1(Hoc2(Content)) <Content ref={myRef} /> 给Content绑定的ref会绑定到Hoc1上,且不会继续向下传递 第一种方法 React.forwardRef =============== 在 Hoc1外面 用React.forwardRef()对ref做处理,用props来传递ref 0. 在高阶组件外面包裹forwardRef,拦截获取ref,增加一个props(xxx={ref}),真实组件通过props.xxx获取 1. 使用时传 ref={XXXX} // 和第二种方法不同的地方 2. 用forwardRef的第二个参数获取 ref 3. 增加一个新的props,用来向下转发ref e.g. forwardedRef={ref} 4. 真实组件中绑定 ref={props.forwardedRef} const Home = (props) => { const connectRef = useRef(null); return ( <div> <Content ref={connectRef} /> </div> ); }; // 被包装组件 const Content = (props) => { return ( <div> <input type="password" ref={props.forwardedRef} /> </div> ); }; // forwardRef的第二个入参可以接收ref,在Hoc外层对ref做处理 export default React.forwardRef((props, ref) => { const Wrapper = React.memo(Content); // Hoc // forwardRef包裹的是Wrapper // 需要在Wrapper中把ref向下传递给真实组件 // Wrapper中增加一个props属性,把ref对象作为props传给子组件 return <Wrapper {...props} forwardedRef={ref} />; }); 第二种方法 ========== 0. 使用时就用一个props来保存ref 1. 使用时传 xxx={ref} // 和第一种方法的不同点 2. 真实组件中绑定 ref={props.xxx} const Home = (props) => { const connectRef = useRef(null); return ( <div> <Content forwardedRef={connectRef} /> </div> ); }; // 定义高阶组件 export const Hoc = (WrappedComponent) => { class Wrapper extends React.Component { render() { return <WrappedComponent {...props} /> } } } // 被包装的组件 const Content = (props) => { return ( <div> <input type="password" ref={props.forwardedRef} /> </div> ); }; // 包装过程 export default Hoc(Content); * */
使用被包装组件的静态方法
/* 使用被包装组件的静态方法 // 被包装组件,增加静态属性和方法 export default class Header extends React.Component { static displayName = 'header'; static showName = () => { console.log(this.displayName); }; render() { return <span>header</span> } }; // HOC export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { return <WrappedComponent {...this.props} /> } }; }; =========== // Hoc包装后的组件拿不到静态方法 import Header from './header'; import Hoc from './Hoc'; const EnhanceHeader = Hoc(Header); export default function Home() { console.log(EnhanceHeader.displayName); // undefined EnhanceHeader.showName(); // undefined return <EnhanceHeader /> } ============= // 解决方法1:拷贝静态方法到HOC上 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { static displayName = WrappedComponent.displayName; // 必须知道被包装组件中有什么静态方法 static showName = WrappedComponent.showName; render() { return <WrappedComponent {...this.props} /> } }; }; ============== // 解决方法2:自动拷贝所有静态属性和方法 import React from 'react'; import hoistNonReactStatic from 'hoist-non-react-statics'; export default function Hoc(WrappedComponent) { class Wrapper extends React.Component { render() { return <WrappedComponent {...this.props} /> } }; hoistNonReactStatic(Wrapper, WrappedComponent); return Wrapper; }; ============== // 解决方法3:导出组件时,额外导入静态属性和方法 class Header extends React.Component { render() { return <span>header</span> } }; const displayName = 'header'; function showName() { console.log(Header.displayName); }; Header.displayName =displayName; Header.showName = showName; export default Header export { displayName, showName } // 导入时 import Header, { displayName, showName } from './header'; import Hoc from './Hoc'; const EnhanceHeader = Hoc(Header); export default function Home() { console.log(displayName); // header showName(); // header return <EnhanceHeader /> } * */
拦截传给被包装组件的props,对props进行增删改
/* 拦截传给被包装组件的props,对props进行增删改 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { // 过滤一些仅在当前Hoc中使用的props,不进行不必要的透传 const { forMeProps, forOtherProps } = this.props; // 在该HOC内部定义,需要注入到被包装组件的额外的属性或方法 const injectProps = some-state-or-method; // 通常是state或实例方法 // 为被包装组件传递上层的props + 额外的props return ( <WrappedComponent injectProps={injectProps} // 传递需要注入的额外props {...forOtherProps} // 透传与后续相关的props /> ) } } } e.g. Hoc接收一个额外的props 'dealUpper',如果为true,将data转换成大写 dealUpper只在该Hoc中使用,所以没必要传给被包装的组件 // HOC export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { render() { const { dealUpper, ...forOtherProps } = this.props; const { data } = forOtherProps; if (dealUpper) { Object.assign(forOtherProps, {data: data.toUpperCase()}) } return <WrappedComponent {...forOtherProps} /> } }; }; // 导出Hoc包装后的增强组件 import React from 'react'; import Hoc from './Hoc1'; class Header extends React.Component { render() { console.log(this.props); // { data: 'ABC' } return <span>{this.props.data}</span> } }; export default Hoc(Header); // 导出包装后的增强组件 // 导入使用 import Header from './header'; const Home = () => { return <Header data={'abc'} dealUpper /> } * */
用HOC提取一些复杂的公共逻辑,在不同组件中扩展不同的功能
/* 用HOC提取一些复杂的公共逻辑,在不同组件中扩展不同的功能 import React from 'react'; export const Hoc = (WrappedComponent, namespace) => { class Wrapper extends React.Component { state = { data: [] } // 抽离的相同请求方法 componentDidMount = () => { const { dispatch } = this.props; dispatch({ type: `${namespace}/queryData`, // 动态请求不同的store payload: {}, callback: res => { if (res) { this.setState({ data: res.data }) } } }) } render() { return <WrappedComponent { ...this.props } data={this.state.data} /> } } } // 包装A组件 import Hoc from './Hoc'; const A = ({ data }) => { ... 省略请求数据的逻辑 return (data.map(item => item)); } export default MyHoc(A, 'a'); // 包装B组件 import Hoc from './Hoc'; const B = ({ data }) => { ... 省略请求数据的逻辑 return ( <ul> { data.map((item, index) => { return <li key={index}><{item}/li> } } </ul> ) } export default Hoc(B, 'b'); * */
让不受控组件变成受控组件
/* 让不受控组件变成受控组件 // Hoc组件 export default function Hoc(WrappedComponent) { return class Wrapper extends React.Component { state = { value: '' }; onChange = (e) => { this.setState({ value: e.target.value }) }; render() { const newProps = { value: this.state.value, onChange: this.onChange }; return <WrappedComponent {...this.props} {...newProps} /> } }; }; // 普通组件 class InputComponent extends React.Component { render() { return <input {...this.props} /> } } // 包装 export default Hoc(InputComponent); * */
反向继承
/* 反向继承(在Hoc中使用被包装组件内部的状态和方法) - 反向继承的组件要是类组件,函数组件不行 export const Hoc = (WrappedComponent) => { class Wrapper extends WrappedComponent { // super ≈ WrappedComponent里面的this render() { if (!this.props.data) { return <span>loading....</span> } else { return super.render() // 调用被包装组件的render()方法 } } } } ==== export default function Hoc(WrappedComponent) { return class extends WrappedComponent { render() { const elementTree = super.render(); // React用Js对象来模拟Dom树结构,可以通过修改Js对象的属性来操纵数据 console.log(elementTree); // 不太了解里面的结构可以打印出来 + 官网cloneElement() 了解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的内容已被劫持` }); return newElementTree; } }; }; * */
渲染劫持
/* 渲染劫持 e.g. 控制组件是否渲染(可以做全局的loading效果,没有数据时显示loading...) // 基本的实现 export const LoadingHoc = (WrappedComponent) => { class Wrapper extends React.Component { render() { if (!this.props.data) { return <span>loading....</span> } else { return <WrappedComponent {...this.props} /> } } } } // 用反向继承实现 export const LoadingHoc = (WrappedComponent) => { class Wrapper extends WrappedComponent { // super ≈ WrappedComponent里面的this render() { if (!this.props.data) { return <span>loading....</span> } else { return super.render() // 调用被包装组件的render()方法 } } } } ====== e.g. 劫持渲染的内容 export default function Hoc2(WrappedComponent) { return class extends WrappedComponent { // 这里用了反向继承 render() { const elementTree = super.render(); // React用Js对象来模拟Dom树结构,可以通过修改Js对象的属性来操纵数据 console.log(elementTree); // 不太了解里面的结构可以打印出来 + 官网cloneElement() 了解一下 const newElementTree = React.cloneElement(elementTree, { children: `你的内容已被劫持` }); return newElementTree; } }; }; * */
配置包装名
/* 配置包装名:在调试工具 React Developer Tools 中更容易被找到 e.g. 高阶组件为Hoc,被包装组件为WrappedComponent, 显示的名字应该是 Hoc(WrappedComponent) // 返回类组件 export default function Hoc(WrappedComponent) { return class extends React.Component { /* 没有在Hoc中定义 static displayName = 'XXX'; - React Developer Tools 中展示的名字是 Anonymous 没有在被包装组件中定义 static displayName = 'XXX'; - React Developer Tools 中展示的名字是 undefined Hoc 在被包装组件中定义 static displayName = 'header'; - React Developer Tools 中展示的名字是 header Hoc *\ static displayName = `Hoc(${WrappedComponent.displayName}); render() { return <WrappedComponent {...this.props} />; } }; } // 返回函数式组件 export default function Hoc(WrappedComponent) { /* return function(props) {} - 在 React Developer Tools 中展示的名字是 Anonymous return function Wrapper(props) {} - 在 React Developer Tools 中展示的名字是 Wrapper * return function Wrapper(props) { return <WrappedComponent {...props} />; }; } ======= export default function Hoc(WrappedComponent) { const Wrapper = (props) => { return <WrappedComponent {...props} />; }; /* 没有在被包装组件中定义 static displayName = 'XXX'; - React Developer Tools 中展示的名字是 undefined Hoc 在被包装组件中定义 static displayName = 'header'; - React Developer Tools 中展示的名字是 header Hoc *\ Wrapper.displayName = `Hoc(${WrappedComponent.displayName})`; return Wrapper; } ===== // 被包裹组件 export default class Header extends React.Component { static displayName = 'header'; render() { return <span>{ this.props.count }</span> } }; * */
不要在render中使用HOC
/* 不要在render中使用HOC e.g. export default class Home extends React.Component { render() { // 每次render都会创建一个新的Wrapper // Wrapper1 !== Wrapper2 // 导致高阶组件会卸载和重新挂载,状态会丢失(e.g. checkbox的选中丢失 | state被清空) × const Wrapper = Hoc(WrappedComponent); return <Wrapper /> } } ========= √ const Wrapper = myHoc(WrappedComponent); export default class Home extends React.Component { render() { return <Wrapper /> } } * */
Hoc的渲染顺序
/* Hoc的渲染顺序 Hoc(Header) componentDidMount: Header -> HOC componentWillUnMount: HOC -> Header * */
HOC 和 Mixin
/* HOC 和 Mixin HOC - 属于函数式编程思想 - 被包裹组件感知不到高阶组件的存在 - 高阶组件返回的组件会在原来的基础上的到增强 Mixin - 混入模式,会在被包装组件上不断增加新的属性和方法 - 被包裹组件可感知 - 需要做处理(命名冲突、状态维护) * */
以上就是React 高阶组件HOC用法归纳的详细内容,更多关于React 高阶组件HOC的资料请关注NICE源码其它相关文章!