【问题标题】:Rendering a functional component as JSX breaks React render reconciliation在 JSX 中渲染功能组件会破坏 React 渲染协调
【发布时间】:2020-10-22 20:16:23
【问题描述】:

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

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 之类的内部内容的情况下,处理此问题的正确方法是什么?谢谢

【问题讨论】:

  • 在这里更新了演示

标签: javascript reactjs


【解决方案1】:

问题是您在每次重新渲染时都创建了一个新组件(函数本身),因为您在 App 中有 inputComponents。如果将类声明移到内部作用域,类组件也会发生相同的行为

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>

【讨论】:

  • 感谢这是一个数据网格filterComponents 并且用户有一个filterState 对象,他们需要将其传递到另一个钩子中,因此他们可能会定义内部组件以访问该状态,但是包裹在useMemo 中将不起作用,因为每次按键都会改变状态。也许我可以想出更好的设计,但我也不想依赖我的用户做聪明的事情。我有一个通过检测和调用 FC 组件作为函数的解决方法,但我发现我无法通过正常的 JSX 方式调用它们很奇怪。无论如何+1
  • “我有一个解决方法,将 FC 组件称为函数”我建议不要将其称为组件。叫它render props。如果用户想使用类组件,他们仍然可以将其作为渲染道具 c: (args) =&gt; &lt;MyClassBasedComponent {...args} /&gt;
  • 谢谢我是这样做的,它可以在没有 hackiness 的情况下工作
猜你喜欢
  • 2020-03-26
  • 2022-11-29
  • 1970-01-01
  • 2018-02-17
  • 2021-10-11
  • 1970-01-01
  • 2020-06-28
  • 2021-08-08
  • 2021-06-07
相关资源
最近更新 更多