高阶组件(Higher-Order Components):
高阶组件(HOC)是 React 中用于重用组件逻辑的高级技术。 HOC 本身不是 React API 的一部分。 它们是从 React 构思本质中浮现出来的一种模式。
具体来说,高阶组件是一个函数,能够接受一个组件并返回一个新的组件。
在我们项目中使用react-redux框架的时候,有一个connect
的概念,这里的connect
其实就是一个高阶组件。也包括类似react-router-dom中的withRouter的概念。
实现高阶组件的方法:
实现高阶组件的方法有如下两种:
- 属性代理。高阶组件通过被包裹的React组件来操作props。
- 反向继承。高阶组件继承于被包裹的React组件。
渲染属性:
解决的是有状态组件的可复用问题。
示例代码:
User的数据并不是app提供的,而是有provider来提供的。
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
class Provider extends Component {
constructor(props) {
super(props);
this.state = {
name: 'Provider--'
};
}
render(){
return this.props.render(this.state.name);
}
}
class User extends Component {
render(){
return (<div>{this.props.data}</div>);
}
}
class App extends Component{
constructor(props){
super(props);
}
render() {
return (<div>
<Provider render={(data) => <User data={data}/> }/>
</div>);
}
componentWillUpdate(){
console.log('will update!');
}
}
ReactDOM.render(<App />, document.getElementById('root'));
上下文Context示例代码:
每个子组件里都需要定义出上下文数据类型,很麻烦。
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import PropTypes from 'prop-types';
class Provider extends Component {
getChildContext(){
return this.props.store;
}
static childContextTypes = {
name: PropTypes.string,
age: PropTypes.number
}
constructor(props) {
super(props);
this.state = {
name: 'Provider--'
};
}
render(){
return this.props.children;
}
}
class BaseUser extends Component {
static contextTypes = {
name: PropTypes.string
}
render(){
return (<div>{this.context.name}</div>);
}
}
class BasePost extends Component {
static contextTypes = {
age: PropTypes.number
};
render(){
return (<div>{this.context.age}</div>);
}
}
// 数据仓库
const store = {
name: 'David',
age: 26
};
class App extends Component{
constructor(props){
super(props);
}
render() {
return (<div>
<Provider store={store}>
<div>
<BaseUser/>
<BasePost/>
</div>
</Provider>
</div>);
}
componentWillUpdate(){
console.log('will update!');
}
}
ReactDOM.render(<App />, document.getElementById('root'));
优化后的代码:
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import PropTypes from 'prop-types';
class Provider extends Component {
getChildContext(){
return this.props.store;
}
static childContextTypes = {
name: PropTypes.string,
age: PropTypes.number
}
constructor(props) {
super(props);
this.state = {
name: 'Provider--'
};
}
render(){
return this.props.children;
}
}
class BaseUser extends Component {
static contextTypes = {
name: PropTypes.string
}
render(){
return (<div>{this.props.name}</div>);
}
}
class BasePost extends Component {
render(){
return (<div>{this.props.age}</div>);
}
}
const connect = (Com) => {
class ConnectComponent extends Component {
static contextTypes = Provider.childContextTypes;
displayName = Com.displayName;
render() {
return <Com {...this.context}/>;
}
}
return ConnectComponent;
};
const User = connect(BaseUser);
const Post = connect(BasePost);
// 数据仓库
const store = {
name: 'David',
age: 26
};
class App extends Component{
constructor(props){
super(props);
}
render() {
return (<div>
<Provider store={store}>
<div>
<User/>
<Post/>
</div>
</Provider>
</div>);
}
componentWillUpdate(){
console.log('will update!');
}
}
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import PropTypes from 'prop-types';
const connect = key => Com => {
class ConnectComponent extends Component {
constructor(props){
super(this);
this.state = {
[key]: store[key]
};
}
render() {
return <Com {...this.state}/>;
}
componentDidMount() {
window.store = new Proxy(store, {
get: (target, key, receiver) => {
console.log('get: ' + key);
return Reflect.get(target, key, receiver);
},
set: (target, key, value, receiver) => {
console.log('set: ' + key + ', value: ' + value);
this.setState({
[key]: value
});
return Reflect.set(target, key, value, receiver);
}
});
}
}
return ConnectComponent;
};
@connect('age')
class User extends Component{
render(){
return (<div>{this.props.age}</div>);
}
}
let store = {
name: 'David',
age: 26
};
class App extends Component{
render() {
return (<User/>);
}
componentWillUpdate(){
console.log('will update!');
}
}
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
上面的代码在运行过程中报错:
Support for the experimental syntax ‘decorators-legacy’ isn’t currently enabled.
解决办法:
- npm install customize-cra react-app-rewired @babel/plugin-proposal-decorators —save
- 项目根目录新建config-overrides.js文件加入以下代码:
const { override, addDecoratorsLegacy } = require(‘customize-cra’);
module.exports = override(
addDecoratorsLegacy()
);
- 修改package.json文件如下:
“scripts”: {
“start”: “react-app-rewired start”,
“build”: “react-app-rewired build”,
“test”: “react-app-rewired test”,
“eject”: “react-app-rewired eject”
}
属性代理预先将业务组件进行了数据的封装,便于获取数据。
高阶组件的另外一种实现方式是反向继承,下面是学习代码。它实现的是交互的封装。
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import * as serviceWorker from './serviceWorker';
import PropTypes from 'prop-types';
const loading = com => {
class LoadingComponent extends com {
render(){
return (<div>
loading
{super.render()}
</div>);
}
showLoading() {
console.log('show loading');
}
hideLoading() {
console.log('hide loading');
}
}
return LoadingComponent;
}
@loading
class User extends Component{
render(){
return (<div>user</div>);
}
componentDidMount() {
this.showLoading();
this.hideLoading();
}
}
class App extends Component{
render() {
return (<User/>);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();