首页 > 解决方案 > 在 JSX 中渲染功能组件会破坏 React 渲染协调

问题描述

我希望我的组件的用户能够传入组件对象,并且我希望允许他们使用基于函数或类的组件。

type InputComponents = Record<string,  React.ComponentType>

这允许 FC 或类组件,但我没有找到关于如何呈现它们的明确指南。

const inputComponents = {
  a: ({ placeholder }) => ( // Functional component
    <input
      type="text"
      value={state.a}
      placeholder={placeholder}
      onChange={(e) => {
        const value = e.currentTarget.value;
        setState((s) => ({ ...s, a: value }));
      }}
    />
  ),
  b: InputClass // Class component
};

两个组件都可以正常渲染,而 Typescript 不会像这样抱怨:

const A = inputComponents.a;
const B = inputComponents.b;

<A placeholder="Type something" />
<B placeholder="Type something" />

但这实际上具有误导性 -功能性 (a) 每次按键都会失去焦点

使功能组件在每次按键时都不会失去焦点的唯一方法是这样的:

inputComponents.a({ placeholder: 'Type something' })

Typescript 甚至不认为这是渲染组件的有效方法,但它是唯一完全有效的方法......它会出现“此表达式不可调用”的错误。并且 Class 组件也失败了,所以我必须这样做:

// Render as JSX for class components and call as a function for FC ones...
Component.prototype.isReactComponent ? <Component placeholder={x} /> : Component({ placeholder: x })

您可以在此处查看实际问题:

function SomeComponent({ inputComponents }) {
  const B = inputComponents.b;
  const C = inputComponents.c;

  return (
    <div className="SomeComponent">
      <p>This FC component doesn't loose focus:</p>
      {inputComponents.a({ placeholder: 'Type something' })}
      <p>This one does:</p>
      <B placeholder="Type something" />
      <p>Rendering a class component as JSX works though:</p>
      <C placeholder="Type something" />
    </div>
  );
}

class InputClass extends React.Component {
  state = {
    value: ''
  };
  render() {
    return (
      <input
        type="text"
        value={this.state.value}
        placeholder={this.props.placeholder}
        onChange={(e) => {
          this.setState({ value: e.currentTarget.value });
        }}
      />
    );
  }
}

function App() {
  const [state, setState] = React.useState({
    a: '',
    b: ''
  });

  const inputComponents = {
    a: ({ placeholder }) => (
      <input
        type="text"
        value={state.a}
        placeholder={placeholder}
        onChange={(e) => {
          const value = e.currentTarget.value;
          setState((s) => ({ ...s, a: value }));
        }}
      />
    ),
    b: ({ placeholder }) => (
      <input
        type="text"
        value={state.b}
        placeholder={placeholder}
        onChange={(e) => {
          const value = e.currentTarget.value;
          setState((s) => ({ ...s, b: value }));
        }}
      />
    ),
    c: InputClass
  };

  return (
    <div className="App">
      <SomeComponent inputComponents={inputComponents} />
    </div>
  );
}

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

<div id="app"></div>

React 坏了吗?或者在不依赖黑客 TS 错误和使用内部东西的情况下处理这个问题的正确方法是什么isReactComponent?谢谢

标签: javascriptreactjs

解决方案


问题是您在每次重新渲染时都创建了一个新组件(函数本身),因为您inputComponentsApp. 如果将类声明移动到内部范围,类组件也会发生相同的行为

inputComponent = {
  c: class InputClass extends Component {}
}

要解决此问题,您可以将组件映射移动到外部范围并传递statesetState作为道具。或通过上下文提供。

function SomeComponent({ inputComponents, args }) {
  const B = inputComponents.b;
  const C = inputComponents.c;

  return (
    <div className="SomeComponent">
      <p>This one does:</p>
      <B placeholder="Type something" {...args} />
      <p>Rendering a class component as JSX works though:</p>
      <C placeholder="Type something" {...args} />
    </div>
  );
}


class InputClass extends React.Component {
  state = {
    value: ''
  };
  render() {
    return (
      <input
        type="text"
        value={this.state.value}
        placeholder={this.props.placeholder}
        onChange={(e) => {
          this.setState({ value: e.currentTarget.value });
        }}
      />
    );
  }
}

  const inputComponents = {
    b: ({ placeholder, state, setState }) => (
      <input
        type="text"
        value={state.b}
        placeholder={placeholder}
        onChange={(e) => {
          const value = e.currentTarget.value;
          setState((s) => ({ ...s, b: value }));
        }}
      />
    ),
    c: InputClass
  };


function App() {
  const [state, setState] = React.useState({
    a: '',
    b: ''
  });

  return (
    <div className="App">
      <SomeComponent inputComponents={inputComponents} args={{state, setState}} />
    </div>
  );
}

ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.development.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.development.min.js"></script>

<div id="app"></div>


推荐阅读