HOC与connect

342 阅读4分钟

高阶组件与函数作为子组件

高阶组件的概念

高阶组件(HOC)是react中的一种设计模式,主要为了解决组件复用的问题,高阶组件接收一个组件作为参数,并且返回一个组件,

例如:

import React from "react";

export default function withTimer(WrappedComponent) {
  return class extends React.Component {
    state = { time: new Date() };
    componentDidMount() {
      this.timerID = setInterval(() => this.tick(), 1000);
    }

    componentWillUnmount() {
      clearInterval(this.timerID);
    }

    tick() {
      this.setState({
        time: new Date()
      });
    }
    render() {
      return <WrappedComponent time={this.state.time} {...this.props} />;
    }
  };
}

这里将WrappedComponent组件作为一个参数并返回一个新的组件,这样将props交给了作为参数的组件,也就是属性代理

函数作为子组件

函数作为子组件同样是为了解决组件复用问题,主要思想在于将函数作为一个子组件,从而可以根据函数参数的变化动态渲染,例如下方示例代码中的animalcolor

import React, { PureComponent } from "react";
import PropTypes from "prop-types";

export default class AdvancedTabSelector extends PureComponent {
  static propTypes = {
    value: PropTypes.object,
    options: PropTypes.array,
    onChange: PropTypes.func,
    children: PropTypes.func
  };

  static defaultProps = {
    value: null,
    options: [],
    onChange: () => {},
    children: () => {}
  };

  render() {
    const { options, value, onChange } = this.props;
    return (
      <div className="tab-selector">
        <ul>
          {options.map(opt => (
            <li
              key={opt.value}
              className={`tab-item ${
                opt.value === this.props.value ? "selected" : ""
              }`}
              onClick={() => this.props.onChange(opt.value)}
            >
              {opt.name}
            </li>
          ))}
        </ul>
        <br />
        <br />
        {this.props.value && this.props.children(this.props.value)}
      </div>
    );
  }
}

const colors = [
  { name: "Red", value: "red" },
  { name: "Blue", value: "blue" },
  { name: "Orange", value: "orange" }
];

const animals = [
  { name: "Tiger", value: "tiger" },
  { name: "Elephant", value: "elephant" },
  { name: "Cow", value: "cow" }
];

export class AdvancedTabSelectorSample extends PureComponent {
  state = {
    color: null
  };
  render() {
    return (
      <div>
        <h3>Select color: </h3>
        <AdvancedTabSelector
          options={colors}
          value={this.state.color}
          onChange={c => this.setState({ color: c })}
        >
          {color => (
            <span
              style={{
                display: "inline-block",
                backgroundColor: color,
                width: "40px",
                height: "40px"
              }}
            />
          )}
        </AdvancedTabSelector>
        <br />
        <br />
        <br />
        <h3>Select animal: </h3>
        <AdvancedTabSelector
          options={animals}
          value={this.state.animal}
          onChange={c => this.setState({ animal: c })}
        >
          {animal => (
            <img width="100px" src={require(`../../images/${animal}.png`)} />
          )}
        </AdvancedTabSelector>
      </div>
    );
  }
}

react-redux中的connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

连接 React 组件与 Redux store。

连接操作不会改变原来的组件类。 反而返回一个新的已与 Redux store 连接的组件类。

参数

  • [mapStateToProps(state, [ownProps]): stateProps] (Function): 如果定义该参数,组件将会监听 Redux store 的变化。任何时候,只要 Redux store 发生改变,mapStateToProps 函数就会被调用。该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。如果你省略了这个参数,你的组件将不会监听 Redux store。如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props,mapStateToProps 也会被调用(例如,当 props 接收到来自父组件一个小小的改动,那么你所使用的 ownProps 参数,mapStateToProps 都会被重新计算)。

    注意:在高级章节中,你需要更好地去控制渲染的性能,所用到的 mapStateToProps() 会返回一个函数。在这种情况下,那个函数将被作为 mapStateToProps() 在独有的组件实例中调用。这样就允许你在每一个实例中去记录。你可以参考 #279 去测试和了解其中的详细内容。但在绝大多数的应用中不会用到。

  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function): 如果传递的是一个对象,那么每个定义在该对象的函数都将被当作 Redux action creator,对象所定义的方法名将作为属性名;每个方法将返回一个新的函数,函数中dispatch方法会将action creator的返回值作为参数执行。这些属性会被合并到组件的 props 中。

如果传递的是一个函数,该函数将接收一个 dispatch 函数,然后由你来决定如何返回一个对象,这个对象通过 dispatch 函数与 action creator 以某种方式绑定在一起(提示:你也许会用到 Redux 的辅助函数 bindActionCreators())。如果你省略这个 mapDispatchToProps 参数,默认情况下,dispatch 会注入到你的组件 props 中。如果指定了该回调函数中第二个参数 ownProps,该参数的值为传递到组件的 props,而且只要组件接收到新 props,mapDispatchToProps 也会被调用。

注意:在高级章节中,你需要更好地去控制渲染的性能,所用到的 mapStateToProps() 会返回一个函数。但在这个例子中,这个函数将被 mapStateToProps() 在独有的组件实例中调用。这样就允许你在每一个实例中去记录。你可以参考 #279 去测试和了解其中的详细内容。但在绝大多数的应用中不会用到。

  • [mergeProps(stateProps, dispatchProps, ownProps): props] (Function): 如果指定了这个参数,mapStateToProps()mapDispatchToProps() 的执行结果和组件自身的 props 将传入到这个回调函数中。该回调函数返回的对象将作为 props 传递到被包装的组件中。你也许可以用这个回调函数,根据组件的 props 来筛选部分的 state 数据,或者把 props 中的某个特定变量与 action creator 绑定在一起。如果你省略这个参数,默认情况下返回 Object.assign({}, ownProps, stateProps, dispatchProps) 的结果。
  • [options] (Object) 如果指定这个参数,可以定制 connector 的行为。
    • [pure = true] (Boolean): 如果为 true,connector 将执行 shouldComponentUpdate 并且浅对比 mergeProps 的结果,避免不必要的更新,前提是当前组件是一个“纯”组件,它不依赖于任何的输入或 state 而只依赖于 props 和 Redux store 的 state。默认值为 true
    • [withRef = false] (Boolean): 如果为 true,connector 会保存一个对被包装组件实例的引用,该引用通过 getWrappedInstance() 方法获得。默认值为 false

connect函数作用是连接react组件与store

import React from "react";
import { bindActionCreators, createStore } from "redux";
import { Provider, connect } from "react-redux";

// Store initial state
const initialState = { count: 0 };

// reducer
const counter = (state = initialState, action) => {
  switch (action.type) {
    case "PLUS_ONE":
      return { count: state.count + 1 };
    case "MINUS_ONE":
      return { count: state.count - 1 };
    case "CUSTOM_COUNT":
      return { count: state.count + action.payload.count };
    default:
      break;
  }
  return state;
};

// Create store
const store = createStore(counter);

// Action creator
function plusOne() {
  // action
  return { type: "PLUS_ONE" };
}

function minusOne() {
  return { type: "MINUS_ONE" };
}

export class Counter extends React.Component {
  render() {
    const { count, plusOne, minusOne } = this.props;
    return (
      <div className="counter">
        <button onClick={minusOne}>-</button>
        <span style={{ display: "inline-block", margin: "0 10px" }}>
          {count}
        </span>
        <button onClick={plusOne}>+</button>
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    count: state.count
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ plusOne, minusOne }, dispatch);
}

const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);

export default class CounterSample extends React.Component {
  render() {
    return (
      <Provider store={store}>
        <ConnectedCounter />
      </Provider>
    );
  }
}

final connect flow