(一)前言
这里提到的设计模式并不是编程通用的设计模式,如常说的单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式等。而是在设计 React 组件时的一些解决方案与技巧,包括以下几种:
(1) 容器与展示组件
(2) 高阶组件
(3) render props
(4) context 模式
(5) 组合组件
(6) 继承
当然概念部分,大家能根据名字猜出,但是我还是要为每种,单独给出demo详细说下。
设计模式本身是因为产品需求,解决特定的物业场景,并非完全需要按照这样,所以个人开发中建议形成自己的开发模式。
(二)容器与展示组件
这可能是目前应用最广泛,也是最简单的,大家最推崇的设计方案。其实大概意思就是,将组件分为两种。
一个为容器组件,负责与外部数据进行交互,比如处理业务逻辑(连接redux),
一个为展示组件,只通过props传递业务逻辑,state控制内部交互,不包含任何业务逻辑,也不与外部数据源(连接redux)进行沟通,现在我来举个例子。
需求: 当我们点击按钮后,会去接口拉去数据,并将返回的数据渲染到视图,当然这种需求,简单可以将全部视图写在一起,但是我们需要使用容器和展示组件的方式来组织代码,那么代码如下
// 容器组件 textContainer.js
import React from 'react';
import Text from './text';
class TextContainer extends React.Component {
state = {
text: '',
};
getData = () => {
// 模拟异步请求
setTimeout(() => {
this.setState({ text: '测试数据' });
}, 1000);
};
render() {
const {
state: {
text,
},
} = this;
return (
<div>
<Text text={text} onClick={this.getData} />
</div>
);
}
}
export default TextContainer;
// 展示组件 text.js
import React from 'react';
import PropTypes from 'prop-types';
class Text extends React.PureComponent {
render() {
const {
props: {
text,
onClick,
},
} = this;
return (
<div>
<div>
接口返回的数据:
{text}
</div>
<button onClick={onClick}>点我啊</button>
</div>
);
}
}
// 这里我建议,不是必须传入的参数,尽量不使用isRequired验证
// 比如input value 肯定是必须的 但是如果不做受控组件,那么回调onChange就不是必须
Text.defaultProps = {
text: '',
onClick: () => null,
};
Text.propTypes = {
text: PropTypes.string,
onClick: PropTypes.func,
};
export default Text;
从上面代码我们看出,我们采用软件设计原则中的“责任分离”, 即让一个模块只负责责任尽量单一
容器展示组件这个模式所解决的问题在于,当我们切换数据获取方式时,只需在容器组件修改相应逻辑即可,展示组件无需做改动。展示组件可完全不变,展示组件有了更高的可复用性。
但该模式的缺点也在于将一个组件分成了两部分,增加了代码跳转的成本。并不是说组件包含从外部获取数据,就要将其拆成容器组件与展示组件。
(三)高阶组件
1.概念:
高阶组件不是组件,是 增强函数,可以输入一个元组件,返回出一个新的增强组件;
高阶组件的主要作用是 代码复用,操作状态和参数;
2. 分类(主要两个。一个属性代理,一个反向继承)
- 属性代理 : 返回出一个组件,它基于被包裹组件进行功能增强;
这里有个获取display name的函数,下面新增的高阶组件都会调用这个方法,就不再次书写了
// 获取display name
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
1.1 默认参数: 可以为组件包裹一层默认参数;
export function ProxyHoc(Component) {
const NewComponent = (props) => {
const newProps = {
name: '高阶组件增加的属性',
age: 1,
};
return <Component {...props} {...newProps} />;
};
return NewComponent;
}
1.2 提取状态: 可以通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:
现在我们来实现一个简单表单高阶组件,不包含表单验证。
// withForm.js
import React from 'react';
import hoistNonReactStatic from 'hoist-non-react-statics';
import { getDisplayName } from './utils';
export default function WithForm(Component) {
class Enhance extends React.Component {
static displayName = `WithForm(${getDisplayName(Component)})`;
state = {
form: {},
};
onChange = key => (e) => {
const { form } = this.state;
form[key] = e.target.value;
this.setState({
form,
});
};
handleSubmit = () => this.state.form;
getField = fieldName => ({
onChange: this.onChange(fieldName),
});
render() {
const newProps = {
...this.props,
getForm: this.handleSubmit,
setFormItem: this