React anti pattern?

2019-01-27 07:18发布

Is the following an anti pattern in React? I like the pattern because it gives me context in static functions when a component has been instantiated. Then later I can import the class and call a static method to modify state. Or can this be done in a better way?

// componentA.js

function bleedContext() {
  ComponentA.staticMethod = ComponentA.staticMethod.bind(this)
}

export default class ComponentA {
  static staticMethod() {
    this.setState({foo: 'bar'})
  }
  
  constructor() {
    this.state = {}
    bleedContext.call(this)
  }
  
  render() {
    return (
      ...
    )
  }
}

// componentB.js

import ComponentA from 'path/to/componentA'

export default class ComponentB {  
  handleClick() {
    ComponentA.staticMethod()
  }
  
  render() {
    return (
      <button onClick={this.handleClick} />
    )
  }
}

1条回答
爱情/是我丢掉的垃圾
2楼-- · 2019-01-27 07:46

This is clearly an antipattern and possibly a mistake, depending on conditions. Static class method shouldn't operate with class instance. staticMethod is bound to specific component instance and uses setState, this could be only justified a class is a singleton (though a singleton is often an antipattern, too). This will result in bugs and memory leaks if more than one class instance is expected, and every React component is expected to have more than one instance, at least for testing.

A proper way for two independent components to interact with each other in React is to have a common parent component that provides this interaction, e.g.:

class ModalContainer extends Component {
  modalRef = React.createRef();

  render() {
    return <>
      <Modal ref={this.modalRef} />
      <SomeComponentThatUsesModal modalRef={this.modalRef} />
    </>;
  }
}

The problem with example above is that this will require to pass modalRef prop deeply if <SomeComponentThatUsesModal> is nested.

This problem is solved with React context or other third-party global state solutions like Redux.

This can be done with React 16.3 context API, considering that Modal class instance has open method:

const ModalContext = React.createContext();

function getModal(modalRef) {
  return {
    open: data => modalRef.current.open(data);
    close: () => modalRef.current.close();
  }
}

class ModalContainer extends Component {
  modalRef = React.createRef();

  render() {
    return <>
      <Modal ref={this.modalRef} />
      <ModalContext.Provider value={getModal(this.modalRef)}>
        {this.props.children}
      </ModalContext.Provider>
    </>;
  }
}

Then for any deeply nested component modal object with open and close methods will be available via context:

const SomeComponentThatUsesModal = props => <div>
  <ModalContext.Consumer>
    {modal => <button onClick={() => modal.open('foo')} />}
  </ModalContext.Consumer>
</div>;

<ModalContainer>
  ...deeply nested component
  <SomeComponentThatUsesModal />
  ...
</ModalContainer>

Here's a demo.

查看更多
登录 后发表回答