使用React getDerivedStateFromProps()

时间:2019-10-20 12:32:21

标签: javascript reactjs

我正在探索React,对生命周期方法和父子沟通有些困惑。具体来说,我试图创建一个包装选择元素并在选择“其他”选项时添加输入框的组件。我已经使用getDerivedStateFromProps()实现了此功能,但是根据documentation,这种生命周期方法应该很少使用。因此,我的问题是:在这种情况下,我还应该了解和使用另一种模式吗?

这是我的代码,值和选项作为道具传递,就像父组件的handleChange()方法一样。因此,当对select或input元素进行更改时,将首先更新父组件的状态,并通过props.value将新值向下传递。

export default class SelectOther extends Component {
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
    }

    static getDerivedStateFromProps(props) {
        let optionIndex = -1;
        for (let i = 0; i < props.options.length; i++) {
            if (props.options[i].value === props.value) {
                optionIndex = i;
                break;
            }
        }
        if (optionIndex > -1) {
            return {
                selected: props.options[optionIndex].value,
                text: "",
                showInput: false
            };
        } else {
            return {
                selected: "",
                text: props.value,
                showInput: true
            };
        }
    }

    handleChange(e) {
        this.props.handleChange({
            "target": {
                "name": this.props.name,
                "value": e.target.value
            }
        });
    }

    render() {
        return (
            <div>
                <label>{ this.props.label }</label>
                <select name={ this.props.name } value={ this.state.selected } onChange={ this.handleChange }>
                    {
                        this.props.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)
                    }
                    <option value="">Other</option>
                </select>
                {
                    this.state.showInput &&
                        <div>
                            <label>{ this.props.label } (specify other)</label>
                            <input type="text" className="form-control" value={ this.state.text } onChange={ this.handleChange }></input>
                        </div>
                }
            </div>
        )
    }
}

1 个答案:

答案 0 :(得分:1)

您可以通过不具有SelectOther的任何状态来简化操作,这是如何传递调度动作以更改值的函数的示例。因为SelectOther是一个纯组件,所以它不会不必要地重新呈现:

//make this a pure component so it won't re render
const SelectOther = React.memo(function SelectOther({
  label,
  name,
  value,
  options,
  handleChange,
}) {
  console.log('in render',name, value);
  const showInput = !options
    .map(o => o.value)
    .includes(value);
  return (
    <div>
      <label>{label}</label>
      <select
        name={name}
        value={showInput ? '' : value}
        onChange={handleChange}
      >
        {options.map(option => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
        <option value="">Other</option>
      </select>
      {showInput && (
        <div>
          <label>{label} (specify other)</label>
          <input
            type="text"
            name={name}
            className="form-control"
            value={value}
            onChange={handleChange}
          ></input>
        </div>
      )}
    </div>
  );
});

const App = () => {
  //create options once during App life cycle
  const options = React.useMemo(
    () => [
      { value: 'one', label: 'one label' },
      { value: 'two', label: 'two label' },
    ],
    []
  );
  //create a state to hold input values and provide
  //  a reducer to create new state based on actions
  const [state, dispatch] = React.useReducer(
    (state, { type, payload }) => {
      //if type of action is change then change the
      //  payload.name field to payload.value
      if (type === 'CHANGE') {
        const { name, value } = payload;
        return { ...state, [name]: value };
      }
      return state;
    },
    //initial state for the inputs
    {
      my_name: '',
      other_input: options[0].value,
    }
  );
  //use React.useCallback to create a callback
  //  function that doesn't change. This would be
  //  harder if you used useState instead of useReducer
  const handleChange = React.useCallback(
    ({ target: { name, value } }) => {
      dispatch({
        type: 'CHANGE',
        payload: {
          name,
          value,
        },
      });
    },
    []
  );
  return (
    <div>
      <SelectOther
        label="label"
        name="my_name"
        value={state.my_name}
        options={options}
        handleChange={handleChange}
      />
      <SelectOther
        label="other"
        name="other_input"
        value={state.other_input}
        options={options}
        handleChange={handleChange}
      />
    </div>
  );
};


//render app
ReactDOM.render(
  <App />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

我本可以在App中使用useState,但是随后我必须使用useEventCallback或对每个输入值使用useState。 following documentation提出了useEventCallback模式,然后紧接着指出我们不推荐这种模式,因此这就是为什么我提出了useReducer解决方案的原因。