1. 程式人生 > >組件的各種概念解析

組件的各種概念解析

另一個 element 屬性 extend clas cti instance mod 靜態方法

1.類組件

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
// 註意: props只讀

2.函數組件

function Welcome(props) { // 組件名首字母必須大些
  return <h1>Hello, {props.name}</h1>; // Props只讀
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById(
‘root‘) ); // 1.我們對<Welcome name="Sara" />元素調用了ReactDOM.render()方法。 // 2.React將{name: ‘Sara‘}作為props傳入並調用Welcome組件。 // 3.Welcome組件將<h1>Hello, Sara</h1>元素作為結果返回。 // 4.React DOM將DOM更新為<h1>Hello, Sara</h1>。

函數組件和類組件是將props屬性轉變成UI

高階組件則是將一個組件轉換成另一個組件

3.高階組件

高階組件就是一個函數,且該函數接受一個組件作為參數,並返回一個新的組件。

高階組件就是一個沒有副作用的純函數。

不要改變原始組件,使用組合

例如:Redux的connect方法

// This function takes a component...
function withSubscription(WrappedComponent, selectData) {
  // ...and returns another component...
  return class extends React.Component {
    constructor(props) {
      super(props);
      this
.handleChange = this.handleChange.bind(this); this.state = { data: selectData(DataSource, props) }; } componentDidMount() { // ... that takes care of the subscription... DataSource.addChangeListener(this.handleChange); } componentWillUnmount() { DataSource.removeChangeListener(this.handleChange); } handleChange() { this.setState({ data: selectData(DataSource, this.props) }); } render() { // ... and renders the wrapped component with the fresh data! // Notice that we pass through any additional props return <WrappedComponent data={this.state.data} {...this.props} />; } }; }

約定:貫穿傳遞不相關props屬性給被包裹的組件

render() {
  // 過濾掉專用於這個階組件的props屬性,
  // 不應該被貫穿傳遞
  const { extraProp, ...passThroughProps } = this.props;

  // 向被包裹的組件註入props屬性,這些一般都是狀態值或
  // 實例方法
  const injectedProp = someStateOrInstanceMethod;

  // 向被包裹的組件傳遞props屬性
  return (
    <WrappedComponent
      injectedProp={injectedProp}
      {...passThroughProps}
    />
  );
}

約定:最大化的組合性

// 不要這樣做……
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))

// ……你可以使用一個函數組合工具
// compose(f, g, h) 和 (...args) => f(g(h(...args)))是一樣的
const enhance = compose(
  // 這些都是單獨一個參數的高階組件
  withRouter,
  connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)

單獨一個參數的高階組件,類似 connect 函數返回的,簽名是Component => Component。輸入和輸出類型相同的函數確實是很容易組合在一起。

約定:包裝顯示名字以便於調試

function withSubscription(WrappedComponent) {
  class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;
  return WithSubscription;
}

function getDisplayName(WrappedComponent) {
  return WrappedComponent.displayName || WrappedComponent.name || ‘Component‘;
}

React Developer Tools便於調試,

註意:

不要在render方法內使用高階組件

原因: (差分算法,渲染性能問題)

render() {
  // 每一次渲染,都會創建一個新的EnhancedComponent版本
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 那引起每一次都會使子對象樹完全被卸載/重新加載
  return <EnhancedComponent />;
}

必須將靜態方法做拷貝

1)可借助hoist-non-react-statics

function enhance(WrappedComponent) {
  class Enhance extends React.Component {/*...*/}
  // 必須得知道要拷貝的方法 :(
  Enhance.staticMethod = WrappedComponent.staticMethod;
  return Enhance;
}

2)分別導出組件自身的靜態方法。

// Instead of...
MyComponent.someFunction = someFunction;
export default MyComponent;

// ...export the method separately...
export { someFunction };

// ...and in the consuming module, import both
import MyComponent, { someFunction } from ‘./MyComponent.js‘;

Refs屬性不能貫穿傳遞

如果你向一個由高階組件創建的組件的元素添加ref應用,那麽ref指向的是最外層容器組件實例的,而不是被包裹的組件。

現在我們提供一個名為 React.forwardRef 的 API 來解決這一問題(在 React 16.3 版本中)

組件的各種概念解析