gpt4 book ai didi

javascript - 在子安装上设置父状态是一种反模式吗?

转载 作者:搜寻专家 更新时间:2023-11-01 04:39:16 24 4
gpt4 key购买 nike

我正在用 React 创建一个表单,并创建了一个 <Field />需要根据特定类型的子元素数量呈现不同包装元素的组件。

例如,如果字段包装单个输入,它应该呈现一个包装器 <div />和一个 <label /> .但是,如果它包装多个输入,它应该呈现一个 <fieldset />。和一个 <legend /> .

至关重要的是, child 不一定是 <Field /> 的直系后代组件,所以计算 childrenReact.Children.count行不通。

我可以通过在子输入挂载时设置父字段状态来轻松完成此操作,例如:

const FormFieldContext = createContext({});

// Simplified Field component
const Field = ({ label, children, ...props }) => {
const [fieldCount, setFieldCount] = useState(0);
const Wrapper = fieldCount > 1 ? 'fieldset' : 'div';
const Label = fieldCount > 1 ? 'legend' : 'label';

return (
<Wrapper>
<Label>{label}</Label>
<FormFieldContext.Provider value={{ setFieldCount }}>
{children}
</FormFieldContext.Provider>
</Wrapper>
);
};

// Inside <Checkbox />
const Checkbox = ({ name, ...props }) => {
const { setFieldCount } = useContext(FormFieldContext);

useLayoutEffect(() => {
setFieldCount(count => count + 1);

return () => {
setFieldCount(count => count - 1);
};
}, [setFieldCount, name]);

return ( /** etc */ );
};


但是我的直觉告诉我这是一个反模式,因为:

  • 它会导致立即重新渲染。
  • 大概不可能让 SSR 变得友好,因为你依赖于基于坐骑的副作用。这意味着 SSR 版本不会考虑 child 。

放弃这个并强制消费者手动设置一个 isFieldset 是否更好?支持 <Field />成分?还是有更聪明的方法来实现这一点?

期望的用法:

{# Renders a div and a label #}
<Field name="email" label="Enter your email">
<TextInput type="email" />
</Field>

{# Renders a legend and a fieldset #}
<Field name="metal" label="Select your metals">
<Checkbox label="Bronze" value="bronze" />
<Checkbox label="Silver" value="silver" />
</Field>
<div class="form-field">
<label for="email">Enter your email</label>
<input type="email" id="email" />
</div>

<fieldset class="form-field">
<legend>Select your metals</label>
<label>
<input type="checkbox" name="metal" value="bronze" /> Bronze
</label>
<label>
<input type="checkbox" name="metal" value="silver" /> Silver
</label>
</fieldset>

最佳答案

实际上可以从 children 中递归获取 React 组件树中的所有子项 Prop ,当你有所有的 child 时,你可以计算有多少类型的组件 CheckBox等等,以确定应该如何包裹 child 。

可能还有更优雅的方法,但在下面的代码示例中我 .reduce使用 getChildrenRecursively 将直接子级转换为包含所有 个子级的扁平数组.

const getChildrenRecursively = (soFar, parent) => {
if(typeof parent === "string") return soFar.concat(parent);

const children = Array.isArray(parent.props.children) ?
parent.props.children :
React.Children.toArray(parent.props.children);

const childCount = children.length;

if(childCount <= 0) {
return soFar.concat(parent);
} else {
return soFar.concat([parent], children.flatMap(child => getChildrenRecursively([], child)));
}
}

不能 100% 确定此解决方案与您的状态逻辑等配合使用的效果如何,但我认为您会发现它很有用。

const getChildrenRecursively = (soFar, parent) => {
if(typeof parent === "string") return soFar.concat(parent);

const children = Array.isArray(parent.props.children) ?
parent.props.children :
React.Children.toArray(parent.props.children);

const childCount = children.length;

if(childCount <= 0) {
return soFar.concat(parent);
} else {
return soFar.concat([parent], children.flatMap(child => getChildrenRecursively([], child)));
}
}

const MyInput = ({placeholder} ) => <input placeholder={placeholder} ></input>;


const MyComp = ({children}) => {
const childArr = React.Children.toArray(children);
// get all children in hierarcy
const flattenedChildren = childArr.reduce(getChildrenRecursively, []);

const numberOfInputs = flattenedChildren
.filter(child => child.type && child.type.name === "MyInput").length;

const Wrapper = numberOfInputs > 1 ? 'fieldset' : 'div';


return (
<div>
<Wrapper className="form-element">
<legend>{numberOfInputs} input(s)</legend>
<div>{children}</div>
</Wrapper>
</div>
)
}


const App = () => {
return (
<div>
<MyComp>
<MyInput placeholder="Some input #1" />
<div>
<span>Some non-input element</span>
</div>
</MyComp>
<MyComp>
<MyInput placeholder="Some input #1" />
<div>
<div>
<MyInput placeholder="Some nested input #2" />
</div>
<div>
<span>Some non-input element</span>
</div>
</div>
</MyComp>
</div>
)
}

ReactDOM.render(<App />, document.querySelector("#app"));
fieldset {
margin: 0px 0px 10px 0px;
border: 1px solid teal;
padding: 20px;
}

div.form-element {
border: 1px solid teal;
padding: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>

关于javascript - 在子安装上设置父状态是一种反模式吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57252830/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com