Next.js

React数据传递

2019-05-30  本文已影响0人  小小的开发人员

props

  当React遇到的元素是用户自定义的组件,它会将JSX属性作为单个对象传递给该组件,这个对象称之为“props”。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

【只读性】
  无论是使用函数或是类来声明一个组件,它决不能修改它自己的props。

【隐藏组件】
  让 render 方法返回 null 可以隐藏组件。

【父传子】
  下面的例子来展示父级如何通过props把数据传递给子级。

class ControlPanel extends Component {
  render() {
    return (
      <div>
        <Counter caption="First"/>
        <Counter caption="Second" initValue={10} />
        <Counter caption="Third" initValue={20} />
        <button onClick={ () => this.forceUpdate() }>
          Click me to re-render!
        </button>
      </div>
    );
  }
}

【读取props】
  下面的例子展示子级如何读取父级传递来的props。

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: props.initValue
    }
  }

【props检查】
  一个组件应该规范以下内容:这个组件支持哪些prop,以及每个prop应该是什么样的格式。React通过propTypes来支持这些功能。

Counter.propTypes = {
  caption: PropTypes.string.isRequired,
  initValue: PropTypes.number
};

Counter.defaultProps = {
  initValue: 0
};

【propTypes】
  要检查组件的属性,需要配置特殊的 propTypes 属性。

import PropTypes from 'prop-types';

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

Greeting.propTypes = {
  name: PropTypes.string
};

react支持如下验证:

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 可以将属性声明为以下 JS 原生类型
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括数字、字符串、子元素或数组)。
  optionalNode: PropTypes.node,

  // 一个 React 元素
  optionalElement: PropTypes.element,

  // 也可以声明属性为某个类的实例
  optionalMessage: PropTypes.instanceOf(Message),

  // 也可以限制属性值是某个特定值之一
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 限制它为列举类型之一的对象
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 一个指定元素类型的数组
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 一个指定类型的对象
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 一个指定属性及其类型的对象
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 也可以在任何 PropTypes 属性后面加上 `isRequired` 后缀
  requiredFunc: PropTypes.func.isRequired,

  // 任意类型的数据
  requiredAny: PropTypes.any.isRequired,

  // 也可以指定一个自定义验证器。它应该在验证失败时返回
  // 一个 Error 对象而不是 `console.warn` 或抛出异常。
  // 不过在 `oneOfType` 中它不起作用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 可以提供一个自定义的 `arrayOf` 或 `objectOf` 验证器,它应该在验证失败时返回一个 Error 对象。 它被用于验证数组或对象的每个值。验证器前两个参数的第一个是数组或对象本身,第二个是它们对应的键。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

【属性默认值】
  可以通过配置 defaultProps 为 props定义默认值。

class Greeting extends React.Component {
  render() {
    return (
      <h1>Hello, {this.props.name}</h1>
    );
  }
}

【限制单个子代】
  使用 PropTypes.element 可以指定只传递一个子代。

import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  render() {
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}

MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};

【子传父】
  React子组件要反馈数据给父组件时,可以使用props。函数类型的props等于让父组件交给子组件一个回调函数,子组件在恰当的时机调用这个函数,可以带上必要的参数,这样就可以反过来把信息传递给父级。
  注意下面的传递过程,onUpdate就是是上面提到的回调函数。

//子组件
class Counter extends Component {
  constructor(props) {
    super(props);
    this.onClickIncrementButton = this.onClickIncrementButton.bind(this); // 修改this的指向,组件里的this指向Counter.prototype,而render函数里return的this指向Counter的实例。
    this.onClickDecrementButton = this.onClickDecrementButton.bind(this);
    this.state = {count: props.initValue}
  }
  onClickIncrementButton() {
    this.updateCount(true);
  }
  onClickDecrementButton() {
    this.updateCount(false);
  }
  updateCount(isIncrement) {
    const previousValue = this.state.count;
    const newValue = isIncrement ? previousValue + 1 : previousValue - 1;
    this.setState({count: newValue})
    this.props.onUpdate(newValue, previousValue)
  }
  render() {
    const {caption} = this.props;
    return (
      <div>
        <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
        <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
        <span>{caption} count: {this.state.count}</span>
      </div>
    );
  }
}
Counter.propTypes = {
  caption: PropTypes.string.isRequired,
  initValue: PropTypes.number,
  onUpdate: PropTypes.func
};
Counter.defaultProps = {
  initValue: 0,
  onUpdate: f => f 
};
export default Counter;
//父组件
class ControlPanel extends Component {
  constructor(props) {
    super(props);
    this.onCounterUpdate = this.onCounterUpdate.bind(this);
    this.initValues = [ 0, 10, 20];
    const initSum = this.initValues.reduce((a, b) => a+b, 0);
    this.state = {sum: initSum};
  }
  onCounterUpdate(newValue, previousValue) {
    const valueChange = newValue - previousValue;
    this.setState({ sum: this.state.sum + valueChange});
  }
  render() {
    return (
      <div>
        <Counter onUpdate={this.onCounterUpdate} caption="First" />
        <Counter onUpdate={this.onCounterUpdate} caption="Second" initValue={this.initValues[1]} />
        <Counter onUpdate={this.onCounterUpdate} caption="Third" initValue={this.initValues[2]} />
        <div>Total Count: {this.state.sum}</div>
      </div>
    );
  }
}
export default ControlPanel;

【局限】
  设想一下,在一个应用中,包含三级或三级以上的组件结构,顶层的祖父级组件想要传递一个数据给最低层的子组件,用props的方式,就只能通过父组件中转,也许中间那一层根本用不上这个props,但是依然需要支持这个props,扮演好搬运工的角色,只因为子组件用得上,这明显违反了低耦合的设计要求。为此,提出了专门的状态管理的概念。

【context】
  在嵌套层级较深的场景中,不想要向下每层都手动地传递需要的 props。这就需要强大的context API了。context 打破了组件和组件之间通过 props传递数据的规范,极大地增强了组件之间的耦合性。而且,就如全局变量一样,context 里面的数据能被随意接触就能被随意修改,每个组件都能够改 context里面的内容会导致程序的运行不可预料。
  React.js 的 context就是这么一个东西,某个组件只要往自己的 context里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的 context只有它的子组件能够访问。其中,react-redux中的provider组件就是使用context实现的。

【使用context】
  通过在MessageList(context提供者)中添加childContextTypesgetChildContext,React会向下自动传递参数,任何组件只要在它的子组件中(这个例子中是Button),就能通过定义contextTypes来获取参数。

const PropTypes = require('prop-types');
class Button extends React.Component {
  render() {
    return (
      <button style={{background: this.context.color}}>
        {this.props.children}
      </button>
    );
  }
}
Button.contextTypes = {
  color: PropTypes.string
};

class Message extends React.Component {
  render() {
    return (
      <div>
        {this.props.text} <Button>Delete</Button>
      </div>
    );
  }
}

class MessageList extends React.Component {
  getChildContext() {
    return {color: "purple"};
  }
  render() {
    const children = this.props.messages.map((message) =>
      <Message text={message.text} />
    );
    return <div>{children}</div>;
  }
}
MessageList.childContextTypes = {
  color: PropTypes.string
};

[注意]如果contextTypes没有定义,那么context将会是个空对象。

上一篇下一篇

猜你喜欢

热点阅读