高阶组件(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;}@loadingclass 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();
