Skip to content
This repository was archived by the owner on Jul 22, 2020. It is now read-only.

fix: async consume for value listener#33

Open
wjw99830 wants to merge 3 commits intozent-contrib:masterfrom
wjw99830:hotfix/async_consume_for_value_listener_20200702
Open

fix: async consume for value listener#33
wjw99830 wants to merge 3 commits intozent-contrib:masterfrom
wjw99830:hotfix/async_consume_for_value_listener_20200702

Conversation

@wjw99830
Copy link
Copy Markdown
Contributor

@wjw99830 wjw99830 commented Jul 2, 2020

为了避免react16.13.x的warning,会在useEffect中调用parent.registerChild,这导致组件首次渲染时useFieldValue总是会返回null,当条件渲染的表单组件使用了destroyOnUnmount并结合前述行为时会在某些场景下出现预期之外的行为,例子如下:

const Input = ({ name, validators, destroyOnUnmount }) => {
  const model = useField(name, '', validators);
  model.destroyOnUnmount = !!destroyOnUnmount;
  const onChange = useCallback(e => model.value = e.target.value, [model]);

  return (
    <div>
      <label>{name}: </label>
      <input value={model.value} onChange={onChange} />
    </div>
  );
};

function Array({ name, children }) {
  const model = useFieldArray(name, undefined, [{}]);

  return <React.Fragment>{children(model)}</React.Fragment>;
}

function App() {
  const form = useForm(FormStrategy.View);
  const [_, __] = React.useState(false);

  return (
    <FormProvider value={form.ctx}>
      <Array name="array">
        {(model: FieldArrayModel<any, any>) => {
          return model.children.map((it, index) => {
            // 组件第一次渲染时id尚未生成,降级到使用index作为key
            return (
              <FieldSet key={it.getModel()?.id || index} model={it}>
                <Input name="input1" />
                <FieldValue name="input1">
                  {input1 => {
                    return input1 !== null ? <Input destroyOnUnmount name="input2" /> : null;
                  }}
                </FieldValue>
              </FieldSet>
            );
          });
        }}
      </Array>
      {/*
         * 点击这个按钮会重新渲染一次App组件,
         * 导致循环渲染中使用的key由index变为
         * Model的id,进而导致表单组件remount,
         * 结果就是input2这个输入框中的值丢失了,
         * 原因是useFieldValue在组件首次渲染时
         * 总会返回null,因此使用了
         * destroyOnUnmount的组件会出现闪烁
         */}
      <button onClick={() => __(_ => !_)}>re-render</button>
    </FormProvider>
  );
}

基于以上场景,需要改变registerChild的调用时机,保证useFieldValue在首次渲染时总能根据name拿到对应的model,考虑到直接恢复代码会重现react 16.13.x中的warning,因此将value-listener.tsx中的消费行为统一改为异步(rxjs的消费总是同步触发的,即数据源发出消息时立即进行消费)。

这么做的缺陷是childRemovechildRegister在发出消息时,整体上进行了两次re-render,第一次是触发childRemovechildRegister发出数据,此时会存在children的移除或新增导致的re-render,第二次是对该消息进行消费时导致的re-render。

@wjw99830
Copy link
Copy Markdown
Contributor Author

wjw99830 commented Jul 2, 2020

整理了一下eslint配置、error常量集中维护

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant