React高階元件實踐

美團點評點餐發表於2017-09-11

前言

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:打點。

如何使用

遵循的原則

1、不要修改原始元件

常見做法是通過修改原元件的prototype來重寫其生命週期方法等(如給WrappedComponent.prototype.componentWillReceiveProps重新賦值)。請使用純函式返回新的元件,因為一旦修改原元件,就失去了元件複用的意義。

2、props保持一致
高階元件在為子元件新增特性的同時,要保持子元件的原有的props不受影響。傳入的元件和返回的元件在props上儘量保持一致。

3、保持可組合性

4、displayName
為了方便除錯,最常見的高階元件命名方式是將子元件名字包裹起來。

5、不要在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方法。

React高階元件實踐

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種:

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

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為例,父元件:

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方法呼叫的示例。

React高階元件實踐

2、在新元件的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>);
        }
    };
}複製程式碼

React高階元件實踐


3、繼承(extends)原元件後返回一個新的class component,如:

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

一些說明:

5:由於class繼承時會先生成父類的示例,所以 Student 的 constructor 會先於WrapperComponent 執行。其次,繼承會覆蓋父類的例項方法,所以在 WrapperComponent定義 componentDidMount 後Student的 componentDidMount 會被覆蓋不會執行。沒有被覆蓋的componentWillMount會被執行。


React高階元件實踐

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

React高階元件實踐

8:此種方法因為可以取到 WrappedComponent 例項的render結果,所以還可以通過React.cloneElement等方法修改由 render 方法輸出的 React 元件樹。

場景舉例

場景1:頁面複用

描述:專案中有兩個UI互動完全相同的頁面,如下圖。但由於服務於不同的業務,資料來源及部分文案有所不同。目前資料獲取統一在lib/utils中進行封裝,如 utils.getShopListA 和 utils.getShopListB。

React高階元件實踐

思路:將獲取資料的函式作為引數傳入,返回高階元件。

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
如上面的第一、二種高階元件方法中所示,常規的通過this是無法獲取你想要的ref,但可以通過ref的回撥函式獲取。

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

componentWillReceiveProps
如上面的第一、二種高階元件方法中所示,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>複製程式碼

比起高階元件,這種方式有一些優勢:

1、程式碼結構上少掉了一層(返回高階元件的)函式封裝。

2、除錯時元件結構更加清晰;

3、從元件複用角度來看,父元件和子元件之間通過children連線,兩個元件其實又完全可以單獨使用,內部耦合較小。當然單獨使用意義並不大,而且高階元件也可以通過組合兩個元件來做到。


同時也有一些劣勢:
1、(返回子元件)函式佔用了父元件原本的props.children;

2、(返回子元件)函式只能進行呼叫,無法劫持劫持原元件生命週期方法或取到static方法;

3、(返回子元件)函式作為子元件包裹在父元件中的方式看起來雖靈活但不夠優雅;

4、由於子元件的渲染控制完全通過在父元件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中的順序從左到右的進行呼叫),然後再是元件的中方法被呼叫。

相關連結


相關文章