工程師與貓
ESC
Content
    ↑↓ navigate open esc close
    Published on

    React Component Design - Prop Collections and Getters

    Authors
    • avatar
      Name
      Alex Yu

    回到Render props component 一開始的例子來看,假設我們使用Toggle的方式改成下面這樣

    function Usage({ onToggle = (...args) => console.log('onToggle', ...args) }) {
      return (
        <Toggle onToggle={onToggle}>
          {({ on, toggle }) => (
            <div>
              {on ? 'The button is on' : 'The button is off'}
              <Switch on={on} onClick={toggle} />
              <hr />
              <button onClick={toggle} aria-pressed={on}>
                {on ? 'on' : 'off'}
              </button>
              <hr />
              <button aria-label="custom-button" onClick={toggle} aria-pressed={on}>
                {on ? 'on' : 'off'}
              </button>
            </div>
          )}
        </Toggle>
      );
    }

    可以看到現在有兩個button可以改變Toggle的狀態,這兩個button接收到的props也有重複的地方,這時候我們可以在Toggle寫一個 function 回傳這些常見的props

    class Toggle extends React.Component {
      //... Same as before
      getStateAndHelpers() {
        return {
          on: this.state.on,
          togglerProps: {
            // collection for common Props
            'aria-pressed': this.state.on,
            onClick: this.toggle,
          },
        };
      }
     
      render() {
        return this.props.children(this.getStateAndHelpers());
      }
    }

    使用Toggle的方式則會變成

    function Usage({ onToggle = (...args) => console.log('onToggle', ...args) }) {
      return (
        <Toggle onToggle={onToggle}>
          {({ on, togglerProps }) => (
            <div>
              <Switch on={on} {...togglerProps} />
              <hr />
              <button {...togglerProps}>{on ? 'on' : 'off'}</button>
              <hr />
              <button aria-label="custom-button" {...togglerProps}>
                {on ? 'on' : 'off'}
              </button>
            </div>
          )}
        </Toggle>
      );
    }

    使用togglerProps這樣的方式就稱為Prop Collections, 上面的例子雖然解決了重複的程式碼,但也有幾個問題:

    • 如果這時候我們想要傳入客製化的onClick, 讓buttononClick除了執行Toggletoggle以外,還要再執行這個onClick function 的話,情況就開始變得複雜
    • 相反的,如果今天我們不希望一些togglerProps裡面的props傳給button的時候,就需要移掉這些props

    Prop Getters

    要解決第一個問題的方法可以透過Prop Getterrs,在原本的togglerProps改用以 function的方式結合我們想要傳入的props

    const callAll =
      (...fns) =>
      (...args) =>
        fns.forEach((fn) => fn && fn(...args));
     
    class Toggle extends React.Component {
      //... Same as before
      getTogglerProps = ({ onClick, ...props } = {}) => ({
        'aria-pressed': this.state.on,
        onClick: callAll(onClick, this.toggle),
        ...props,
      });
     
      getStateAndHelpers() {
        return {
          on: this.state.on,
          toggle: this.toggle,
          getTogglerProps: this.getTogglerProps,
        };
      }
     
      render() {
        return this.props.children(this.getStateAndHelpers());
      }
    }
     
    function Usage({
      onToggle = (...args) => console.log('onToggle', ...args),
      onButtonClick = () => console.log('onButtonClick'),
    }) {
      return (
        <Toggle onToggle={onToggle}>
          {({ on, getTogglerProps }) => (
            <div>
              <Switch {...getTogglerProps({ on })} />
              <hr />
              <button
                {...getTogglerProps({
                  'aria-label': 'custom-button',
                  onClick: onButtonClick,
                  id: 'custom-button-id',
                })}
              >
                {on ? 'on' : 'off'}
              </button>
            </div>
          )}
        </Toggle>
      );
    }

    這樣的方式解決了我們想要傳客製化的 props 到Toggle, 但卻不是完美。一來是這樣的方式沒辦法決定 function 執行的順序;二來這樣還是無法解決如果有些在getTogglerPropsprops不希望傳給 children component 的時候,還是需要透過一些額外的方式移除這些props