⾼阶组件⽤法及封装
⾼阶组件(HOC)是 React 中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC ⾃身不是 React API 的⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。简单点说,就是组件作为参数,返回值也是组件的函数,它是纯函数,不会修改传⼊的组件,也不会使⽤继承来复制其⾏为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作⽤。
使⽤HOC的原因
- 抽取重复代码,实现组件复⽤:相同功能组件复⽤
- 条件渲染,控制组件的渲染逻辑(渲染劫持):权限控制。
- 捕获/劫持被处理组件的⽣命周期,常⻅场景:组件渲染性能追踪、⽇志打点。
HOC实现⽅式
属性代理
使⽤组合的⽅式,将组件包装在容器上,依赖⽗⼦组件的⽣命周期关系来;
- 返回stateless的函数组件
- 返回class组件
- 操作props
// 可以通过属性代理,拦截⽗组件传递过来的porps并进⾏处理。
// 返回⼀个⽆状态的函数组件
function HOC(WrappedComponent) {
const newProps = { type: 'HOC' };
return props => <WrappedComponent {...props} {...newProps}/>;
}
// 返回⼀个有状态的 class 组件
function HOC(WrappedComponent) {
return class extends React.Component {
render() {
const newProps = { type: 'HOC' };
return <WrappedComponent {...this.props} {...newProps}/>;
}
};
}
- 抽象state
// 通过属性代理⽆法直接操作原组件的state,可以通过props和cb抽象state
function HOC(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
name: '',
};
this.onChange = this.onChange.bind(this);
}
onChange = (event) => {
this.setState({
name: event.target.value,
})
}
render() {
const newProps = {
name: {
value: this.state.name,
onChange: this.onChange,
},
};
return <WrappedComponent {...this.props} {...newProps} />;
}
};
}
// 使⽤
@HOC
class Example extends Component {
render() {
return <input name="name" {...this.props.name} />;
}
}
- 通过props实现条件渲染
// 通过props来控制是否渲染及传⼊数据
import * as React from 'react';
function HOC (WrappedComponent) {
return (props) => (
<div>
{
props.isShow ? (
<WrappedComponent
{...props}
/>
) : <div>暂⽆数据</div>
}
</div>
);
}
export default HOC;
- 其他元素wrapper传⼊的组件
function withBackgroundColor(WrappedComponent) {
return class extends React.Component {
render() {
return (
<div style={{ backgroundColor: '#ccc' }}>
<WrappedComponent {...this.props} {...newProps} />
</div>
);
}
};
}
反向继承
使⽤⼀个函数接受⼀个组件作为参数传⼊,并返回⼀个继承了该传⼊组件的类组件,且在返回组件的render() ⽅法中返回 super.render() ⽅法
const HOC = (WrappedComponent) => {
return class extends WrappedComponent {
render() {
return super.render();
}
}
}
- 允许HOC通过this访问到原组件,可以直接读取和操作原组件的state/ref等;
- 可以通过super.render()获取传⼊组件的render,可以有选择的渲染劫持;
- 劫持原组件⽣命周期⽅法
function HOC(WrappedComponent){
const didMount = WrappedComponent.prototype.componentDidMount;
// 继承了传⼊组件
return class HOC extends WrappedComponent {
async componentDidMount(){
// 劫持 WrappedComponent 组件的⽣命周期
if (didMount) {
await didMount.apply(this);
}
...
}
render(){
//使⽤ super 调⽤传⼊组件的 render ⽅法
return super.render();
}
}
}
- 读取/操作原组件的state
function HOC(WrappedComponent){
const didMount = WrappedComponent.prototype.componentDidMount;
// 继承了传⼊组件
return class HOC extends WrappedComponent {
async componentDidMount(){
if (didMount) {
await didMount.apply(this);
}
// 将 state 中的 number 值修改成 2
this.setState({ number: 2 });
}
render(){
//使⽤ super 调⽤传⼊组件的 render ⽅法
return super.render();
}
}
}
- 条件渲染
const HOC = (WrappedComponent) =>
class extends WrappedComponent {
render() {
if (this.props.isRender) {
return super.render();
} else {
return <div>暂⽆数据</div>;
}
}
}
- 修改react树
// 修改返回render结果
function HigherOrderComponent(WrappedComponent) {
return class extends WrappedComponent {
render() {
const tree = super.render();
const newProps = {};
if (tree && tree.type === 'input') {
newProps.value = 'something here';
}
const props = {
...tree.props,
...newProps,
};
const newTree = React.cloneElement(tree, props,
tree.props.children);
return newTree;
}
};
}
属性代理和反向继承对比
- 属性代理:从“组合”⻆度出发,有利于从外部操作wrappedComp,可以操作props,或者在
wrappedComp 外加⼀些拦截器(如条件渲染等); - 反向继承:从“继承”⻆度出发,从内部操作wrappedComp,可以操作组件内部的state,⽣命周期和render等,功能更加强⼤;
Hooks详解
Hooks是react16.8以后新增的钩⼦API;
⽬的:增加代码的可复⽤性,逻辑性,弥补⽆状态组件没有⽣命周期,没有数据管理状态state的缺陷。
为什么要使⽤Hooks?
- 开发友好,可扩展性强,抽离公共的⽅法或组件,Hook 使你在⽆需修改组件结构的情况下复⽤状态逻辑;
- 函数式编程,将组件中相互关联的部分根据业务逻辑拆分成更⼩的函数;
- class更多作为语法糖,没有稳定的提案,且在开发过程中会出现不必要的优化点,Hooks⽆需学习复杂的函数式或响应式编程技术;
常⻅Hooks
useState
const [number, setNumber] = useState(0);
- setState⽀持stateless组件有⾃⼰的state;
- ⼊参:具体值或⼀个函数;
- 返回值:数组,第⼀项是state值,第⼆项负责派发数据更新,组件渲染;注意:setState会让组件重新执⾏,所以⼀般需要配合useMemo或useCallback;
const DemoState = (props) => {
/* number为此时state读取值 ,setNumber为派发更新的函数 */
const [number, setNumber] = useState(0) /* 0为初始值 */
return (
<div>
<span>{ number }</span>
<button onClick={ ()=> {
setNumber(number + 1)
console.log(number) /* 这⾥的number是不能够即使改变的,返回0 */
}}
/>
</div>
)
}
// 当更新函数之后,state的值是不能即时改变的,只有当下⼀次上下⽂执⾏的时候,state值才
随之改变
——————————————————————————————————————————
const a =1
const DemoState = (props) => {
/* useState 第⼀个参数如果是函数 则处理复杂的逻辑,返回值为初始值 */
let [number, setNumber] = useState(()=>{
// number
return a === 1 ? 1 : 2
}) /* 1为初始值 */
return (<div>
<span>{ number }</span>
<button onClick={ ()=>setNumber(number+1) } ></button>
</div>)
}
useEffect
- 使⽤条件:当组件init、dom render完成、操纵dom、请求数据(如componentDidMount)等;
- 不限制条件,组件每次更新都会触发useEffect -->componentDidUpdate 与componentwillreceiveprops;
- useEffect 第⼀个参数为处理事件,第⼆个参数接收数组,为限定条件,当数组变化时触发事件,为[]只在组件初始化时触发;
- useEffect第⼀个参数有返回时,⼀般⽤来消除副作⽤(如去除定时器、事件绑定等);