一句话介绍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的资料请关注极客世界其它相关文章! |
请发表评论