Recoil 中的动态原子键

Dac*_*d3r 3 reactjs recoiljs

我正在尝试创建一个动态表单,其中表单输入字段是根据 API 返回的数据呈现的。

由于atom需要有一个唯一的键,我尝试将它包装在一个函数中,但是每次我更新字段值或重新安装组件(尝试更改选项卡)时,我都会收到一条警告:

””

我在这里做了一个小的运行示例https://codesandbox.io/s/zealous-night-e0h4jt?file=/src/App.tsx(代码如下):

import React, { useEffect, useState } from "react";
import { atom, RecoilRoot, useRecoilState } from "recoil";
import "./styles.css";

const textState = (key: string, defaultValue: string = "") =>
  atom({
    key,
    default: defaultValue
  });

const TextInput = ({ id, defaultValue }: any) => {
  const [text, setText] = useRecoilState(textState(id, defaultValue));

  const onChange = (event: any) => {
    setText(event.target.value);
  };

  useEffect(() => {
    return () => console.log("TextInput unmount");
  }, []);

  return (
    <div>
      <input type="text" value={text} onChange={onChange} />
      <br />
      Echo: {text}
    </div>
  );
};

export default function App() {
  const [tabIndex, setTabIndex] = useState(0);

  // This would normally be a fetch request made by graphql or inside useEffect
  const fields = [
    { id: "foo", type: "text", value: "bar" },
    { id: "hello", type: "text", value: "world" }
  ];

  return (
    <div className="App">
      <RecoilRoot>
        <form>
          <button type="button" onClick={() => setTabIndex(0)}>
            Tab 1
          </button>
          <button type="button" onClick={() => setTabIndex(1)}>
            Tab 2
          </button>

          {tabIndex === 0 ? (
            <div>
              <h1>Fields</h1>
              {fields.map((field) => {
                if (field.type === "text") {
                  return (
                    <TextInput
                      key={field.id}
                      id={field.id}
                      defaultValue={field.value}
                    />
                  );
                }
              })}
            </div>
          ) : (
            <div>
              <h1>Tab 2</h1>Just checking if state is persisted when TextInput
              is unmounted
            </div>
          )}
        </form>
      </RecoilRoot>
    </div>
  );
}
Run Code Online (Sandbox Code Playgroud)

这在后坐力的情况下也可能吗?我的意思是它似乎有效,但我不能忽视这些警告。

jse*_*ksn 6

这个答案展示了如何使用记忆手动管理原子的多个实例。

但是,如果您的defaultValue每个使用实例都不会更改,那么 Recoil 已经提供了一个实用程序,可以为您处理此创建和记忆操作:atomFamily。我将引用上一个链接中的一些相关信息(但请阅读全部内容以充分理解):

...您可以通过记忆模式自己实现这一点。但是,Recoil为您提供了这种模式的atomFamily实用程序。原子族代表原子的集合。当您调用时,atomFamily它将返回一个函数,该函数RecoilState根据您传入的参数提供原子。

本质atomFamily上提供了从参数到原子的映射。您只需为 提供一个密钥atomFamily,它就会为每个底层原子生成一个唯一的密钥。这些原子密钥可用于持久性,因此必须在应用程序执行过程中保持稳定。参数也可能在不同的调用点生成,我们希望等效的参数使用相同的底层原子。因此,参数使用值相等而不是引用相等atomFamily。这对可用于参数的类型施加了限制。atomFamily接受基本类型,或者可以包含数组、对象或基本类型的数组或对象。

下面是一个工作示例,展示了在为每个输入使用状态实例时如何使用idand defaultValue(作为元组的唯一值组合)作为参数:atomFamily

TS游乐场

body { font-family: sans-serif; }
input[type="text"] { font-size: 1rem; padding: 0.5rem; }
Run Code Online (Sandbox Code Playgroud)
<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/recoil@0.6.1/umd/recoil.min.js"></script><script src="https://unpkg.com/@babel/standalone@7.17.7/babel.min.js"></script><script>Babel.registerPreset('tsx', {presets: [[Babel.availablePresets['typescript'], {allExtensions: true, isTSX: true}]]});</script>
<script type="text/babel" data-type="module" data-presets="tsx,react">

// import ReactDOM from 'react-dom';
// import type {ReactElement} from 'react';
// import {atomFamily, RecoilRoot, useRecoilState} from 'recoil';

// This Stack Overflow snippet demo uses UMD modules instead of the above import statments
const {atomFamily, RecoilRoot, useRecoilState} = Recoil;

const textInputState = atomFamily<string, [id: string, defaultValue?: string]>({
  key: 'textInput',
  default: ([, defaultValue]) => defaultValue ?? '',
});

type TextInputProps = {
  id: string;
  defaultValue?: string;
};

function TextInput ({defaultValue = '', id}: TextInputProps): ReactElement {
  const [value, setValue] = useRecoilState(textInputState([id, defaultValue]));

  return (
    <div>
      <input
        type="text"
        onChange={ev => setValue(ev.target.value)}
        placeholder={defaultValue}
        {...{value}}
      />
    </div>
  );
}

function App (): ReactElement {
  const fields = [
    { id: 'foo', type: 'text', value: 'bar' },
    { id: 'hello', type: 'text', value: 'world' },
  ];

  return (
    <RecoilRoot>
      <h1>Custom defaults using atomFamily</h1>
      {fields.map(({id, value: defaultValue}) => (
        <TextInput key={id} {...{defaultValue, id}} />
      ))}
    </RecoilRoot>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));

</script>
Run Code Online (Sandbox Code Playgroud)