React高階組件實踐

前言

React高階組件,即 Higher-Order Component,其 官方解釋是:
A higher-order component is a function that takes a component and returns a new component.
一個傳入一個組件,返回另外一個組件的函數,其概念與高階函數的將函數做爲參數傳入相似。
用代碼來解釋就是:
const EnhancedComponent = higherOrderComponent(WrappedComponent);複製代碼
以上經過 higherOrderComponent 函數返回的 EnhancedComponent 就是一個高階組件。因此簡單來講,高階只是一種設計模式(pattern),並不是一種新的組件類型。

爲什麼使用

關於高階組件解決的問題能夠簡單歸納成如下幾個方面:
  • 代碼複用:這是高階組件最基本的功能。組件是React中最小單元,兩個類似度很高的組件經過將組件重複部分抽取出來,再經過高階組件擴展,增刪改props,可達到組件可複用的目的;
  • 條件渲染:控制組件的渲染邏輯,常見case:鑑權;
  • 生命週期捕獲/劫持:藉助父組件子組件生命週期規則捕獲子組件的生命週期,常見case:打點。

如何使用

遵循的原則

一、不要修改原始組件html

常見作法是經過修改原組件的prototype來重寫其生命週期方法等(如給WrappedComponent.prototype.componentWillReceiveProps從新賦值)。請使用純函數返回新的組件,由於一旦修改原組件,就失去了組件複用的意義。

二、props保持一致
高階組件在爲子組件添加特性的同時,要保持子組件的原有的props不受影響。傳入的組件和返回的組件在props上儘可能保持一致。

三、保持可組合性前端

四、displayName
爲了方便調試,最多見的高階組件命名方式是將子組件名字包裹起來。

五、不要在render方法內部使用高階組件
render中的高階組件會在每次render時從新mount,以前組件內部的state也會丟失。


使用方法對比
高階組件使用有幾種不一樣的方式,在介紹這幾種方式以前,咱們能夠幾個方面來分析他們之間的差別。一個React組件有如下幾個重要組成部分:
  • props
  • state
  • ref
  • 生命週期方法
  • static方法
  • React 元素樹
補充一下:爲了訪問DOM elements(focus事件、動畫、使用第三方dom操做庫)時咱們會用到ref屬性。它能夠聲明在DOM Element和Class Component上,沒法聲明在Functional Components上。一開始ref聲明爲字符串的方式基本不推薦使用,在將來的react版本中可能不會再支持,目前官方推薦的用法是ref屬性接收一個回調函數。這個函數執行的時機爲:
  1. 組件被掛載後,回調函數被當即執行,回調函數的參數爲該組件的具體實例。
  2. 組件被卸載或者原有的ref屬性自己發生變化時,回調也會被當即執行,此時回調函數參數爲null,以確保內存泄露。

因此不一樣方式的對比能夠從如下幾個方面進行(原組件即傳入組件):
  1. 原組件所在位置:如可否被包裹或包裹其餘組件;
  2. 可否讀取到或操做原組件的props
  3. 可否讀取、操做(編輯、刪除)原組件的state
  4. 可否經過ref訪問到原組件中的dom元素
  5. 是否影響原組件某些生命週期等方法
  6. 是否取到原組件static方法
  7. 可否劫持原組件生命週期方法
  8. 可否渲染劫持

使用方法介紹
下面咱們來介紹下高階組件的使用方法,在介紹以前,咱們假設有一個簡單的組件Student,有name和age兩個經過props傳入後初始化的state,一個年齡輸入框,一個點擊後focus輸入框的按鈕和一個sayHello的static方法。


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>);
    }
}複製代碼

總的來講,高階組件中返回新組件的方式有如下3種:

一、直接返回一個stateless component,如:react

function EnhanceWrapper(WrappedComponent) {
   const newProps = {
        source: 'app',
    };
    return props => <WrappedComponent {...props} {...newProps} />;
}複製代碼
stateless component沒有本身的內部state及生命週期,因此這種方式經常使用於對組件的props進行簡單統一的邏輯處理。
  1. √ 原組件所在位置(可否被包裹或包裹其餘組件)
  2. √ 可否取到或操做原組件的props
  3. 乄 可否取到或操做state
  4. 乄 可否經過ref訪問到原組件中的dom元素
  5. X  是否影響原組件生命週期等方法
  6. √ 是否取到原組件static方法
  7. X  可否劫持原組件生命週期
  8. 乄 可否渲染劫持
一些說明:
3:能夠經過props 和回調函數對state進行操做。
4:由於 stateless component 並沒有實例,因此不要說 ref ,this都沒法訪問。可是能夠經過子組件的ref回調函數來訪問子組件的ref。
8:能夠經過props來控制是否渲染及傳入數據,但對 WrappedComponent 內部render的控制並非很強。

關於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);複製代碼

子組件中須要調用父組件傳入的ref回調函數:
<input 
   ref={(input) => { 
       this.inputElement = input; 
    }}
/>複製代碼
改成:
<input 
    ref={(input) => { 
        this.inputElement = input; 
        this.props.inputRef(input); 
    }}
/>複製代碼
這樣父組件能夠訪問到子組件中的input元素。
如下是ref調用和static方法調用的示例。


二、在新組件的render函數中返回一個新的class component,如:
function EnhanceWrapper(WrappedComponent) {
    return class WrappedComponent extends React.Component {
        render() {
           return <WrappedComponent {...this.props} />;
        }
    }
}複製代碼
  1. √ 原組件所在位置(可否被包裹或包裹其餘組件)
  2. √ 可否取到或操做原組件的props
  3. 乄 可否取到或操做state
  4. 乄 可否經過ref訪問到原組件中的dom元素
  5. √ 是否影響原組件生命週期等方法
  6. √ 是否取到原組件static方法
  7. X  可否劫持原組件生命週期
  8. 乄 可否渲染劫持
一些說明:
3:能夠經過props 和回調函數對state進行操做。
4:ref雖然沒法直接經過this來直接訪問,但依舊能夠利用上面所用的回調函數方式訪問。
7:高階組件和原組件的生命週期徹底是React父子組件的生命週期關係。
8:和第一種相似,能夠經過props來控制是否渲染及傳入數據,但對WrappedComponent內部render的控制並非很強。
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();
        }
    }
}複製代碼
此種方式最大特色是下容許 HOC 經過 this 訪問到 WrappedComponent,因此能夠讀取和操做state/ref/生命週期方法。
  1. √ 原組件所在位置(可否被包裹或包裹其餘組件)
  2. √ 可否取到或操做原組件的props
  3. √ 可否取到或操做state
  4. √ 可否經過ref訪問到原組件中的dom元素
  5. √ 是否影響原組件生命週期等方法
  6. √ 是否取到原組件static方法
  7. √ 可否劫持原組件生命週期
  8. √ 可否渲染劫持
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會被執行。後端



7:雖然生命週期重寫會被覆蓋,但能夠經過其餘方式來劫持生命週期。
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>);
        }
    };
}複製代碼


8:此種方法由於能夠取到 WrappedComponent 實例的render結果,因此還能夠經過React.cloneElement等方法修改由 render 方法輸出的 React 組件樹。

場景舉例

場景1:頁面複用

描述:項目中有兩個UI交互徹底相同的頁面,以下圖。但因爲服務於不一樣的業務,數據來源及部分文案有所不一樣。目前數據獲取統一在lib/utils中進行封裝,如 utils.getShopListA 和 utils.getShopListB。


思路:將獲取數據的函數做爲參數傳入,返回高階組件。

components/ShopList.jsx
import React from 'react';

class ShopList extends React.Component {
    componentWillMount() {
    }

    render() {
        // 使用this.props.data渲染
    }
}

export default ShopList;複製代碼

common/shopListWithFetching.jsx
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;複製代碼

page/SholistA.jsx
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'));複製代碼

page/SholistB.jsx
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'));複製代碼

場景2:頁面鑑權

描述:最近有一個新業務要上線,包含有一系列相關頁面。如今須要對其中幾個頁面增長白名單功能,若是不在白名單中的用戶訪問這些頁面只進行文案提示,不展現業務數據。一週後去掉白名單,對所有用戶開放。
以上場景中有幾個條件:
  • 幾個頁面:鑑權代碼不能重複寫在頁面組件中;
  • 只進行文案提示:鑑權過程在頁面部分生命週期(業務數據請求)以前;
  • 一週後去掉白名單:鑑權應該徹底與業務解耦,增長或去除鑑權應該最小化影響原有邏輯。
思路:將鑑權流程封裝,經過高階組件像一件衣服穿在在業務組件外面。

假設原有頁面(以page1和page2爲例)代碼以下:
pages/Page1.jsx
import React from 'react';

class Page1 extends React.Component {
   componentWillMount() {
       // 獲取業務數據
   }
   render() {
       // 頁面渲染
   }
}
export default Page1複製代碼
pages/Page2.jsx
import React from 'react';

class Page2 extends React.Component {
  componentWillMount() {
      // 獲取業務數據
  }
  render() {
      // 頁面渲染
  }
}
export default Page2

複製代碼
思路:經過高階組件將頁面頂層組件封裝,頁面加載時請求後端鑑權接口,在render方法中增長渲染邏輯,鑑權失敗展現文案,成功渲染原頁面組件,請求業務數據。
高階組件(components/AuthWrapper.jsx),鑑權方法名爲whiteListAuth(lib/utils.js)。
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;複製代碼

增長鑑權後的頁面
pages/Page1.jsx
import React from 'react';
import AuthWrapper from '../components/AuthWrapper';

class Page1 extends React.Component {
  componentWillMount() {
      // 獲取業務數據
  }
  render() {
      // 頁面渲染
  }
}
// export default Page1
export default AuthWrapper(Page1);複製代碼
pages/Page2.jsx
import React from 'react';
import AuthWrapper from '../components/AuthWrapper';

class Page2 extends React.Component {
 componentWillMount() {
     // 獲取業務數據
 }
 render() {
     // 頁面渲染
 }
}
// export default Page2
export default AuthWrapper(Page2);

複製代碼
這樣鑑權與業務徹底解耦,也避免鑑權失敗狀況下多餘的數據請求,只須要增長/刪除一行代碼,改動一行代碼,便可增長/去除白名單的控制。

場景3:日誌及性能打點

描述:全部使用React的前端項目頁面須要增長PV,UV,性能打點。每一個項目的不一樣頁面頂層組件生命週期中分別增長打點代碼無疑會產生大量重複代碼。

思路:經過extends方法返回高階組件,劫持原頁面組件的生命週期。具體可期待其餘小夥伴後續的文章。設計模式


高階組件常見問題

Ref
如上面的第1、二種高階組件方法中所示,常規的經過this是沒法獲取你想要的ref,但能夠經過ref的回調函數獲取。

Static方法丟失
如上面的第1、二種高階組件方法中所示,高階組件對子組件包裝以後會返回一個容器組件,這意味着新組件不包含任何子組件中包含的靜態方法。爲了解決這個問題,應該將靜態方法拷貝到容器組件以後,再將其返回。可使用 hoist-non-react-statics 來自動的拷貝全部非React的靜態方法。固然另外一個解決方案是將組件自身和靜態方法分別導出。

componentWillReceiveProps
如上面的第1、二種高階組件方法中所示,props層層傳遞,值變化時必然會引發一些維護上的困難。

經常使用高階組件庫

React-Redux - connect
使用過React-Redux的同窗都知道,組件中訪問全局state數據,咱們須要調用connect函數,如 官方示例中:
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)複製代碼
其中 TodoList 是一個React組件。如下是 connect函數源代碼
return function connect(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  {
    pure = true,
    areStatesEqual = strictEqual,
    areOwnPropsEqual = shallowEqual,
    areStatePropsEqual = shallowEqual,
    areMergedPropsEqual = shallowEqual,
    ...extraOptions
  } = {}
) {
    return connectHOC(selectorFactory, {...})
}複製代碼
上面的connectHOC的默認值就是下面的 connectAdvanced
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)
}複製代碼
能夠看出,connect函數傳入mapStateToProps等參數,執行結果是返回另外一個函數。給這個函數傳入原始組件(WrappedComponent),會返回另外一個新的組件(Connect),props也傳入了這個組件。

Recompose is a React utility belt for function components and higher-order components.
以 withHandlers 爲例:
/* 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複製代碼

Relay - RelayContainer
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;
}複製代碼

Function as Child Components

在React社區中,還有另外一種相似高階組件的方式叫作 Function as Child Components。它的思路是將函數(執行結果是返回新的組件)做爲子組件傳入,在父組件的render方法中執行此函數,能夠傳入特定的參數做爲子組件的props。
以上面的Student組件爲例:
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鏈接,兩個組件其實又徹底能夠單獨使用,內部耦合較小。固然單獨使用意義並不大,並且高階組件也能夠經過組合兩個組件來作到。


同時也有一些劣勢:
一、(返回子組件)函數佔用了父組件本來的props.children;

二、(返回子組件)函數只能進行調用,沒法劫持劫持原組件生命週期方法或取到static方法;

三、(返回子組件)函數做爲子組件包裹在父組件中的方式看起來雖靈活但不夠優雅;

四、因爲子組件的渲染控制徹底經過在父組件render方法中調用(返回子組件)函數,沒法經過shouldComponentUpdate來作性能優化。


因此這兩種方式各有優劣,可根據具體場景選擇。

關於Mixins

在使用ES6語法寫組件以前,組件複用咱們一般使用mixin方式,而使用ES6語法以後mixin再也不支持,因此如今組內的項目中也再也不使用。而mixin做爲一種抽象和共用代碼的方案,許多庫(好比react-router)都依賴這一功能。
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.
在React官方文章 Mixins Considered Harmful 中闡述了一些Mixins存在的問題:
  1. Mixins introduce implicit dependencies
  2. Mixins cause name clashes
  3. Mixins cause snowballing complexity


二者生命週期上的差別

HOC的生命週期依賴於其實現,而mixin中除了render以外其餘的生命週期方法均可以重複且會調用,但不能夠設置相同的屬性或者包含相同名稱的普通方法。重複的生命週期調用方法的順序是:mixin方法首先會被調用(根據mixins中的順序從左到右的進行調用),而後再是組件的中方法被調用。

相關連接

相關文章
相關標籤/搜索