React 高阶组件HOC用法归纳
程序员文章站
2022-06-26 21:43:58
一句话介绍hoc何为高阶组件(hoc),根据官方文档的解释:“高阶组件是react中复用组件逻辑的一项高级技术。它不属于react api的组成部分,它是从react自身组合性质中抽离出来的一种模式。...
一句话介绍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的资料请关注其它相关文章!