zoukankan      html  css  js  c++  java
  • React:受控组件与非受控组件混用实战

    原文链接:React: hybrid controlled components in action

    FBI WARNING: 对于提倡无状态设计的React来说这可能是一种反模式。

    众所周知,有很多web组件可以通过用户交互改变它的状态,如<input><select>,或者我们常用的一些在线富文本编辑器。这些组件在日常开发中不是很起眼 - 我们可以通过在其中键入内容或设置value属性来轻松修改它的值。但是,由于React是单向数据绑定的,在React中使用这些组件不是很好控制它的状态:

    1.一个维护自身Stateinput组件不能从外部修改它的状态;
    2.一个input组件的值如果由外部props传入,则其值受外部控制;

    基于上述两个特点,React提出了受控组件非受控组件的概念。

    受控组件

    一个受控的input组件接受一个value属性,渲染一个<input>元素,其值反应value属性的值。

    受控组件不保存其自身的内部状态; 该组件纯粹根据props呈现内容。

    也就是说,如果我们有一个通过props设置valueinput组件,它将持续显示props.value,即使你通过键盘输入字符。换句话说,你的组件是只读的。

    很多流行的组件都是以这种方式运行。如果我们将类似value这样的属性从这些组件中移除,你好发现它会变成一个“死亡的组件”, - 谁会爱死一个死人呢?

    在使用受控组件时,您必须始终传递一个value属性,同时注册一个onChange处理程序,才能以使它们处于活动状态,如此一来,它的上层组件会变的复杂和混乱。

    非受控组件

    一个不带value属性的input组件是非受控组件。用户的任何输入都将立即被反应在渲染元素上。

    不受控制的组件保持其自身的内部状态。

    这样的组件运作起来更像原生的组件。可是等等!我们如何像以前那样通过普通的js操作input.value = xxx来更改输入值呢?

    遗憾的是,你没有办法从外部改变其内部的状态,因为它是不受控制。

    混用受控组件和非受控组件

    那么为什么不构建一个既受控又不受控制的组件呢?根据React对(非)受控组件的定义,我们可以得到一些启示和原则:

    原则一

    props.value始终具有比内部state.value更高的优先级。

    当设置了props.value,我们应该始终使用其值代替state.value渲染组件,所以我们可以定义一个displayValue getter属性:

    get displayValue() {  
      return this.props[key] !== undefined ?
        this.props[key] : this.state[internalKey];
    }
    

    然后在render功能:

    render() {  
      return (<div>{this.displayValue}</div>);
    }
    

    原则二

    组件的任何更改都应同步到内部state.value,然后通过props.onChange请求更新上层组件的状态。

    将值同步到state.value可以确保组件在不受控制时能够呈现最新值。请求外部更新告诉上层组件执行更改props.value,因此受控组件也可以呈现正确的值。

    handleChange(newVal) {  
      if (newVal === this.state.value) return;
    
      this.setState({
        value: newVal,
      }, () => {
        this.props.onChange && this.props.onChange(newVal);
      });
    }
    

    原则三

    当组件接收到新的props时将props.value映射到state.value

    同步props.valuestate.value的值是非常关键的,它能及时修正内部状态并保证handleChange的正确运转。

    componentWillReceiveProps(nextProps) {  
      const controlledValue = nextProps.value;
    
      if (controlledValue !== undefined &&
          controlledValue !== this.state.value
      ) {
        this.setState({
          value: controlledValue,
        });
      }
    }
    

    原则四

    确保优先的值发生变化才更新组件。

    这可以防止组件进行不必要的重新渲染,例如,受控组件在内部state.value更改时不应触发重新渲染。

    shouldComponentUpdate(nextProps, nextState) {  
      if (nextProps.value !== undefined) {
        // controlled, use `props.value`
        return nextProps.value !== this.props.value;
      }
    
      // uncontrolled, use `state.value`
      return nextState.value !== this.state.value;
    }
    

    实施方案

    综上所有原则,我们可以创建一个装饰器如下:

    /**
     * Optimize hybrid controlled component by add some method into proto
     *
     * Usage:
     *   @hybridCtrl
     *   class App extends React.Component {
     *     ...
     *   }
     *
     *   @hybridCtrl('specified_prop_to_assign')
     *   class App extends React.Component {
     *     ...
     *   }
     *
     *  @hybridCtrl('specified_prop_to_assign', '_internal_prop')
     *   class App extends React.Component {
     *     ...
     *   }
     */
    
    import shallowCompare from 'react-addons-shallow-compare';
    
    const noop = () => {};
    
    const optimizer = (Component, key = 'value', internalKey = `_${key}`) => {
      // need `this`
      function shallowCompareWithExcept(nextProps, nextState) {
        const props = {
          ...nextProps,
          [key]: this.props[key], // patched with same value
        };
    
        const state = {
          ...nextState,
          [internalKey]: this.state[internalKey],
        };
    
        return shallowCompare(this, props, state);
      }
    
      const {
        shouldComponentUpdate = shallowCompareWithExcept,
        componentWillReceiveProps = noop,
      } = Component.prototype;
    
      Object.defineProperty(Component.prototype, 'displayValue', {
        get: function getDisplayValue() {
          // prefer to use `props[key]`
          return this.props[key] !== undefined ?
            this.props[key] : this.state[internalKey];
        },
      });
    
      // assign new props to state
      Object.defineProperty(Component.prototype, 'componentWillReceiveProps', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: function componentWillReceivePropsWrapped(nextProps) {
          const controlledValue = nextProps[key];
          if (controlledValue !== undefined &&
              controlledValue !== this.state[internalKey]
          ) {
            this.setState({
              [internalKey]: this.mapPropToState ?
                this.mapPropToState(controlledValue) : controlledValue,
            });
          }
    
          componentWillReceiveProps.call(this, nextProps);
        },
      });
    
      // patch shouldComponentUpdate
      Object.defineProperty(Component.prototype, 'shouldComponentUpdate', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: function shouldComponentUpdateWrapped(nextProps, nextState) {
          let result = true;
    
          if (nextProps[key] !== undefined) {
            // controlled, use `props[key]`
            result &= (nextProps[key] !== this.props[key]);
          } else {
            // uncontrolled, use `state[internalKey]`
            result &= (nextState[internalKey] !== this.state[internalKey]);
          }
    
          // logic OR rocks
          return result ||
            shouldComponentUpdate.call(this, nextProps, nextState);
        },
      });
    
      return Component;
    };
    
    export const hybridCtrl = (keyOrComp, internalKey) => {
      if (typeof keyOrComp === 'function') {
        return optimizer(keyOrComp);
      }
    
      return (Component) => optimizer(Component, keyOrComp, internalKey);
    };
    

    这个装饰器的使用方法如下:

    import PropTypes from 'prop-types'
    @hybridCtrl
    class App extends React.Component {  
      static propTypes = {
        value: PropTypes.any,
      }
    
      state = {
        _value: '',
      }
    
      mapPropToState(controlledValue) {
        // your can do some transformations from `props.value` to `state._value`
      }
    
      handleChange(newVal) {
        // it's your duty to handle change events and dispatch `props.onChange`
      }
    }
    
    

    总结

    1.我们为什么需要混合受控组件和非受控组件?(什么场合需要使用杂交组件?)

    我们需要创建同时受控和非受控制的组件,就像原生组件一样。

    2.混合的主要思想是什么?

    同时维护props.valuestate.value。但props.value的值在渲染时具有更高的优先级,state.value反映了组件的真实值。

  • 相关阅读:
    Codeforces Round #234A
    Java中的字符串
    Codeforces Round #376A (div2)
    node源码详解 (一)
    node源码详解(三)
    node源码详解(四)
    修改bootstrap modal模态框的宽度
    Bootstrap 模态框(Modal)插件
    JavaScript局部变量和全局变量的理解
    Javascript:谈谈JS的全局变量跟局部变量
  • 原文地址:https://www.cnblogs.com/star91/p/React-hun-he-shou-kong-zu-jian-yu-fei-shou-kong-zu.html
Copyright © 2011-2022 走看看