A higher-order component is a function that takes a component and returns a new component.
const EnhancedComponent = higherOrderComponent(WrappedComponent);複製代碼
一、不要修改原始組件html
三、保持可組合性前端
class Student extends React.Component {
static sayHello() {
console.log('hello from Student'); // eslint-disable-line
}
constructor(props) {
super(props);
console.log('Student constructor'); // eslint-disable-line
this.focus = this.focus.bind(this);
}
componentWillMount() {
console.log('Student componentWillMount'); // eslint-disable-line
this.setState({
name: this.props.name,
age: this.props.age,
});
}
componentDidMount() {
console.log('Student componentDidMount'); // eslint-disable-line
}
componentWillReceiveProps(nextProps) {
console.log('Student componentWillReceiveProps'); // eslint-disable-line
console.log(nextProps); // eslint-disable-line
}
focus() {
this.inputElement.focus();
}
render() {
return (<div style={outerStyle}>
<p>姓名:{this.state.name}</p>
<p>
年齡:
<input
style={inputStyle}
value={this.state.age}
ref={(input) => {
this.inputElement = input;
}}
/>
</p>
<p>
<input
style={buttonStyle}
type="button"
value="focus input"
onClick={this.focus}
/>
</p>
</div>);
}
}複製代碼
一、直接返回一個stateless component,如:react
function EnhanceWrapper(WrappedComponent) {
const newProps = {
source: 'app',
};
return props => <WrappedComponent {...props} {...newProps} />;
}複製代碼
關於ref的訪問,以上面的子組件Student爲例,父組件:git
import Student from '../components/common/Student';
function EnhanceWrapper(WrappedComponent) {
let inputElement = null;
function handleClick() {
inputElement.focus();
}
function wrappedComponentStaic() {
WrappedComponent.sayHello();
}
return props => (<div>
<WrappedComponent
inputRef={(el) => { inputElement = el; }}
{...props}
/>
<input
type="button"
value="focus子組件input"
onClick={handleClick}
/>
<input
type="button"
value="調用子組件static"
onClick={wrappedComponentStaic}
/>
</div>);
}
const WrapperComponent = EnhanceWrapper(ShopList);複製代碼
<input
ref={(input) => {
this.inputElement = input;
}}
/>複製代碼
<input
ref={(input) => {
this.inputElement = input;
this.props.inputRef(input);
}}
/>複製代碼
function EnhanceWrapper(WrappedComponent) {
return class WrappedComponent extends React.Component {
render() {
return <WrappedComponent {...this.props} />;
}
}
}複製代碼
function EnhanceWrapper(WrappedComponent) {
return class WrapperComponent extends React.Component {
static wrappedComponentStaic() {
WrappedComponent.sayHello();
}
constructor(props) {
super(props);
console.log('WrapperComponent constructor'); // eslint-disable-line
this.handleClick = this.handleClick.bind(this);
}
componentWillMount() {
console.log('WrapperComponent componentWillMount'); // eslint-disable-line
}
componentDidMount() {
console.log('WrapperComponent componentDidMount'); // eslint-disable-line
}
handleClick() {
this.inputElement.focus();
}
render() {
return (<div>
<WrappedComponent
inputRef={(el) => { this.inputElement = el; }}
{...this.props}
/>
<input
type="button"
value="focus子組件input"
onClick={this.handleClick}
/>
<input
type="button"
value="調用子組件static"
onClick={this.constructor.wrappedComponentStaic}
/>
</div>);
}
};
}複製代碼
三、繼承(extends)原組件後返回一個新的class component,如:github
function EnhanceWrapper(WrappedComponent) {
return class WrappedComponent extends WrappedComponent {
render() {
return super.render();
}
}
}複製代碼
function EnhanceWrapper(WrappedComponent) {
return class WrapperComponent extends WrappedComponent {
constructor(props) {
super(props);
console.log('WrapperComponent constructor'); // eslint-disable-line
this.handleClick = this.handleClick.bind(this);
}
componentDidMount(...argus) {
console.log('WrapperComponent componentDidMount'); // eslint-disable-line
if (didMount) {
didMount.apply(this, argus);
}
}
handleClick() {
this.inputElement.focus();
}
render() {
return (<div>
{super.render()}
<p>姓名:{this.state.name}</p>
<input
type="button"
value="focus子組件input"
onClick={this.handleClick}
/>
<input
type="button"
value="調用子組件static"
onClick={WrapperComponent.sayHello}
/>
</div>);
}
};
}複製代碼
一些說明:redux
5:因爲class繼承時會先生成父類的示例,因此 Student 的 constructor 會先於WrapperComponent 執行。其次,繼承會覆蓋父類的實例方法,因此在 WrapperComponent定義 componentDidMount 後Student的 componentDidMount 會被覆蓋不會執行。沒有被覆蓋的componentWillMount會被執行。後端
function EnhanceWrapper(WrappedComponent) {
const willMount = WrappedComponent.prototype.componentWillMount;
const didMount = WrappedComponent.prototype.componentDidMount;
return class WrapperComponent extends WrappedComponent {
constructor(props) {
super(props);
console.log('WrapperComponent constructor'); // eslint-disable-line
this.handleClick = this.handleClick.bind(this);
}
componentWillMount(...argus) {
console.log('WrapperComponent componentWillMount'); // eslint-disable-line
if (willMount) {
willMount.apply(this, argus);
}
}
componentDidMount(...argus) {
console.log('WrapperComponent componentDidMount'); // eslint-disable-line
if (didMount) {
didMount.apply(this, argus);
}
}
handleClick() {
this.inputElement.focus();
}
render() {
return (<div>
{super.render()}
<p>姓名:{this.state.name}</p>
<input
type="button"
value="focus子組件input"
onClick={this.handleClick}
/>
<input
type="button"
value="調用子組件static"
onClick={WrapperComponent.sayHello}
/>
</div>);
}
};
}複製代碼
import React from 'react';
class ShopList extends React.Component {
componentWillMount() {
}
render() {
// 使用this.props.data渲染
}
}
export default ShopList;複製代碼
import ShopList from '../components/ShopList.jsx';
function shopListWithFetching(fetchData, defaultProps) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentWillMount() {
fetchData().then((list) => {
this.setState({
data: list,
});
}, (error) => {
console.log(error); // eslint-disable-line
});
}
render() {
return <ShopList data={this.state.data} {...defaultProps} {...this.props} />;
}
};
}
export default shopListWithFetching;複製代碼
import React from 'react';
import ReactDOM from 'react-dom';
import getShopListA from '../lib/utils';
import shopListWithFetching from '../common/shopListWithFetching.jsx';
const defaultProps = {
emptyMsg: '暫無門店數據',
};
const SholistA = shopListWithFetching(getShopListA, defaultProps);
ReactDOM.render(<SholistA />, document.getElementById('app'));複製代碼
import React from 'react';
import ReactDOM from 'react-dom';
import getShopListB from '../lib/utils';
import shopListWithFetching from '../components/ShopList.jsx';
const defaultProps = {
emptyMsg: '暫無合做的門店',
};
const SholistB = shopListWithFetching(getShopListB, defaultProps);
ReactDOM.render(<SholistB />, document.getElementById('app'));複製代碼
import React from 'react';
class Page1 extends React.Component {
componentWillMount() {
// 獲取業務數據
}
render() {
// 頁面渲染
}
}
export default Page1複製代碼
import React from 'react';
class Page2 extends React.Component {
componentWillMount() {
// 獲取業務數據
}
render() {
// 頁面渲染
}
}
export default Page2
複製代碼
import React from 'react';
import { whiteListAuth } from '../lib/utils';
/**
* 白名單權限校驗
* @param WrappedComponent
* @returns {AuthWrappedComponent}
* @constructor
*/
function AuthWrapper(WrappedComponent) {
class AuthWrappedComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
permissionDenied: -1,
};
}
componentWillMount() {
whiteListAuth().then(() => {
// success
this.setState({
permissionDenied: 0,
});
}, (error) => {
this.setState({
permissionDenied: 1,
});
console.log(error);
});
}
render() {
if (this.state.permissionDenied === -1) {
return null;
}
if (this.state.permissionDenied) {
return <div>功能即將上線,敬請期待~</div>;
}
return <WrappedComponent {...this.props} />;
}
}
return AuthWrappedComponent;
}
export default AuthWrapper;複製代碼
import React from 'react';
import AuthWrapper from '../components/AuthWrapper';
class Page1 extends React.Component {
componentWillMount() {
// 獲取業務數據
}
render() {
// 頁面渲染
}
}
// export default Page1
export default AuthWrapper(Page1);複製代碼
import React from 'react';
import AuthWrapper from '../components/AuthWrapper';
class Page2 extends React.Component {
componentWillMount() {
// 獲取業務數據
}
render() {
// 頁面渲染
}
}
// export default Page2
export default AuthWrapper(Page2);
複製代碼
思路:經過extends方法返回高階組件,劫持原頁面組件的生命週期。具體可期待其餘小夥伴後續的文章。設計模式
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)複製代碼
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
return connectHOC(selectorFactory, {...})
}複製代碼
export default function connectAdvanced() {
return function wrapWithConnect(WrappedComponent) {
class Connect extends Component {
render() {
// 返回
return createElement(WrappedComponent, this.addExtraProps(selector.props))
}
}
}
// Similar to Object.assign
return hoistStatics(Connect, WrappedComponent)
}複製代碼
Recompose is a React utility belt for function components and higher-order components.
/* eslint-disable no-console */
import { Component } from 'react'
import createEagerFactory from './createEagerFactory'
import setDisplayName from './setDisplayName'
import wrapDisplayName from './wrapDisplayName'
import mapValues from './utils/mapValues'
const withHandlers = handlers => BaseComponent => {
const factory = createEagerFactory(BaseComponent)
class WithHandlers extends Component {
cachedHandlers = {}
handlers = mapValues(
typeof handlers === 'function' ? handlers(this.props) : handlers,
(createHandler, handlerName) => (...args) => {
const cachedHandler = this.cachedHandlers[handlerName]
if (cachedHandler) {
return cachedHandler(...args)
}
const handler = createHandler(this.props)
this.cachedHandlers[handlerName] = handler
if (
process.env.NODE_ENV !== 'production' &&
typeof handler !== 'function'
) {
console.error(
// eslint-disable-line no-console
'withHandlers(): Expected a map of higher-order functions. ' +
'Refer to the docs for more info.'
)
}
return handler(...args)
}
)
componentWillReceiveProps() {
this.cachedHandlers = {}
}
render() {
return factory({
...this.props,
...this.handlers,
})
}
}
return WithHandlers
}
export default withHandlers複製代碼
function createContainerComponent(
Component: React.ComponentType<any>,
spec: RelayContainerSpec,
): RelayContainerClass {
const ComponentClass = getReactComponent(Component);
class RelayContainer extends React.Component<$FlowFixMeProps,
{
queryData: {[propName: string]: mixed},
rawVariables: Variables,
relayProp: RelayProp,
},
> {
render(): React.Node {
if (ComponentClass) {
return (
<ComponentClass
{...this.props}
{...this.state.queryData}
ref={'component'} // eslint-disable-line react/no-string-refs
relay={this.state.relayProp}
/>
);
} else {
// Stateless functional.
const Fn = (Component: any);
return React.createElement(Fn, {
...this.props,
...this.state.queryData,
relay: this.state.relayProp,
});
}
}
}
return RelayContainer;
}複製代碼
class StudentWithAge extends React.Component {
componentWillMount() {
this.setState({
name: '小紅',
age: 25,
});
}
render() {
return (
<div>
{this.props.children(this.state.name, this.state.age)}
</div>
);
}
}複製代碼
<StudentWithAge>
{
(name, age) => {
let studentName = name;
if (age > 22) {
studentName = `大學畢業的${studentName}`;
}
return <Student name={studentName} />;
}
}
</StudentWithAge>複製代碼
一、代碼結構上少掉了一層(返回高階組件的)函數封裝。性能優化
二、調試時組件結構更加清晰;bash
三、從組件複用角度來看,父組件和子組件之間經過children鏈接,兩個組件其實又徹底能夠單獨使用,內部耦合較小。固然單獨使用意義並不大,並且高階組件也能夠經過組合兩個組件來作到。
二、(返回子組件)函數只能進行調用,沒法劫持劫持原組件生命週期方法或取到static方法;
四、因爲子組件的渲染控制徹底經過在父組件render方法中調用(返回子組件)函數,沒法經過shouldComponentUpdate來作性能優化。
90% of the time you don't need mixins, in general prefer composition via high order components. For the 10% of the cases where mixins are best (e.g. PureRenderMixin and react-router's Lifecycle mixin), this library can be very useful.
HOC的生命週期依賴於其實現,而mixin中除了render以外其餘的生命週期方法均可以重複且會調用,但不能夠設置相同的屬性或者包含相同名稱的普通方法。重複的生命週期調用方法的順序是:mixin方法首先會被調用(根據mixins中的順序從左到右的進行調用),而後再是組件的中方法被調用。