React很适合构建大应用,除了它灵活的 JSX 语法外还有个很重要的原因,就是它的高阶组件和组件的反向继承。我们在构建大型应用中,高阶组件和反向继承让很多业务场景很高效的实现。开发中我们肯定会将UI组件库,重新封装成符合要求设计的组件,供大家统一调用;以及我们在开发过程中也会存在UI组件库迁移,两个库的组件名称以及API名称存在不一致情况,这就需要我们将要组件名称和API名称进行重写了;当然最重要的一个业务场景是,我们的项目完成度很高时,已经有很多节点调用的一个复杂组件,需要自己在它的基础上进行一些拓展和重写,但又存在牵一发而动全身的风险,这时我们的高阶组件和反向继承就发挥了巨大的作用。
高阶组件
React 对高阶组件的官方解释就是高阶组件是:参数为组件,返回值为新组件的函数。
我们在构建应用中,用高阶组件(属性代理模式)实现了装饰模式(了解下 ES7 中的装饰器)。
业务一:组件的二次封装
将 antd 组件库进行重写,使它的组件库风格符合设计要求。
index.js 调用
import MyButton from 'myButton.js'
import MyTree from 'myTree.js'
export {
MyButton,
MyTree
}
复制代码
我们将 antd 的 Button 封装成我们自己的 MyButton 样式风格。colors 类似于 antd 中的 type,特性有 primary、danger等颜色。下面只写colors 为 danger 的 Button。
import 'myButton.less'
export default class MyButton extends Component {
render() {
let { onClick, colors, className, ...others } = this.props
return (
<Button
className=`${className} my-button-wrap button-${colors}`
onClick={(e) => {
onClick(e)
}
{...others}
>
</Button>
)
}
}
复制代码
myButton.less 部分实现
.button-danger {
background-color: #e14c46;
color: #fff;
&:hover {
background-color: #d7332c;
color:#fff;
}
&:disabled {
background-color: #F19B96;
color: #F2F2F2 ;
&:hover {
color: #F2F2F2 ;
background-color: #fdcac6;
}
}
}
复制代码
业务二:组件的项目迁移
我们在项目中,遇到框架迁移是会有 API 名称不同的问题。我们将 antd 中树形控件的 API,改写成我们之前项目中的 API。比如,我们将之前的 onExpand 改写成 myExpand。当然,我们还可以在里面加入更加复杂的功能,这就要依据我们的业务需要来确定功能了。这里只是进行演示,所以尽量简单。
const { TreeNode } = Tree;
export default class MyTree extends Component {
render() {
let { myClick, myExpand, ...others } = this.props
return (
<Tree
onCheck={() => {
myCheck()
}
onExpand = {(expandedKeys, {expanded: bool, node}) => {
myExpand(expandedKeys, {expanded: bool, node})
}
{...others}
>
</Tree>
)
}
}
MyTree.myTreeNode = TreeNode
复制代码
业务三:对之前的组件进行装饰
当然我们有时写一个业务,完全可以在,之前的组件的基础上,额外进行一些修饰,就可以满足需求,用装饰器的方式,就可以节省很多的代码。这样高阶组件就起到很重要的作用。(装饰器装饰器是一个实验性的 JavaScript 提案。)
import OldComponent from "./Component.js"
const newComponent = (WrapComponent) => {
return class extends WrapComponent {
render() {
let props = this.props
let newProps = {
@click:() => {},
...this.props
}
return <WrapComponent />
}
}
}
export default newComponent(OldComponent)
复制代码
业务四:工厂模式实现
得到程序的需求前,将实现方法相同的部分抽出,写入到高阶函数内部。然后,函数中以不同的组件,以及不同的函数为参数,返回功能相似但完全不同的组件。
function hocFn({
WrappedComponent,
data,
callback = () => console.log('没有传入回调函数')
}) {
// ...并返回另一个组件...
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: data
};
}
componentDidMount() {
callback()
}
render() {
return <WrappedComponent data={this.state.data} {...this.props} />;
}
};
}
//创建不同表格()
let TreeTable = hocFn(Tree, data1, () => console.log('创建一个树形表格'))
let EditTable = hocFn(Form, data1, () => console.log('创建一个可编辑表格'))
复制代码
高阶组件之反向继承
反向继承最核心的两个作用,一个是渲染劫持,另一个是操作state吧。反向继承有两种写法,两种写法存在不同的能力。
实现一个子组件 ComponentChild.js
export default class ComponentChild extends Component {
constructor(props) {
super(props)
this.state = {
num: 2019
}
}
componentDidMount() {
console.log("child component Did Mount")
}
clickComponent() {
console.log("Component click")
}
render() {
return (
<div>{this.state.num}</div>
)
}
}
复制代码
装饰组件直接传入
imoprt ComponentChild from './ComponentChild.js'
let iihoc = WrapComponet => class extends WrapComponet {
constructor(props) {
super(props)
this.state = {
num: 2000
}
}
componentDidMount() {
console.log('iihoc componentDidMount')
this.clickComponent()
}
return (
<div>
<div onClick={this.clickComponent}>iiHoc 点击</div>
<div><WrapComponent /></div>
</div>
)
}
export default iihoc(ComponentChild)
复制代码
这样的方式比属性代理好的是,在外部组件可以调用被继承组件的方法。但不能将被继承的 state 和 钩子覆盖掉。
调用 super.render()
imoprt ComponentChild from './ComponentChild.js'
let iihoc = WrapComponet => class extends WrapComponet {
constructor(props) {
super(props)
this.state = {
num: 2000
}
}
componentDidMount() {
console.log('iihoc componentDidMount')
this.clickComponent()
}
return (
<div>
<div onClick={this.clickComponent}>iiHoc 点击</div>
<div>{super.render()}</div>
</div>
)
}
export default iihoc(ComponentChild)
复制代码
这样的方式,外部组件的 state 可以将,被继承组件的 state 和 钩子函数彻底覆盖掉。同时,外部组件也可以调用被继承组件的方法。
render() {
let renderchild = super.render();
let newProps = {
...renderTree.props,
onClick: this.handleClick
};
const newRenderchild = React.cloneElement(
renderchild,
newProps
);
return newRenderchild;
}
复制代码
解决嵌套地狱(HOOK)
高阶组件、render props 等其他抽象层组成的组件会形成“嵌套地狱”。在 React16.8 中的HOOK是对嵌套地狱的一种解决方式吧。