我有一个通过 REST API 与数据库交互的表单,并提供了几个受控的 Fluent UI 组件。对于多选字段,我构建了一个组件,该组件显示一个带有任意数量的受控 Checkbox 组件的 Stack。下面是组件定义。
class MultiChoiceField extends React.Component
{
static contextType = FormContext;
static displayName = "MultiChoiceField";
#handlers = { change: {} };
/**
* Initializes the component using the information provided in the {@link Item} provided by the {@link FormContext}.
* @constructor
* @param {Object} props The properties provided for this component.
*/
constructor(props)
{
super(props);
this.state = { value: {} };
}
/**
* Set up the component once it is added to the DOM. Context isn't available in the constructor, so we set up the
* value here.
* @function
* @param {Object} nextProps The value that will be assigned to `this.props`.
* @param {Object} nextContext The {@link FormContext} that will be assigned to `this.context`.
* @public
* @returns {void}
*/
componentDidMount(nextProps, nextContext)
{
const choices = nextProps?.Field?.Choices?.results || [];
let value = nextContext?.Item?.[nextProps.FieldName] || {};
value = Array.isArray(value) ? value : (value.results || []);
this.setState({
value: choices.reduce((result, choice) => ({ ...result, [choice]: value.indexOf(choice) >= 0 }), {})
});
}
/**
* Update the component when it receives new props or context information.
* @function
* @param {Object} nextProps The value that will be assigned to `this.props`.
* @param {Object} nextContext The {@link FormContext} that will be assigned to `this.context`.
* @public
* @returns {void}
*/
componentWillReceiveProps(nextProps, nextContext)
{
const choices = nextProps?.Field?.Choices?.results;
let value = nextContext.Item?.[nextProps.FieldName] || {};
value = Array.isArray(value) ? value : (value.results || []);
this.setState({
value: choices.reduce((result, choice) => ({ ...result, [choice]: value.indexOf(choice) >= 0 }), {})
});
}
/**
* Get an event handler for the specified choice.
* @function
* @param {string} name The choice with which this event handler is associated.
* @public
* @returns {function} An event handler for the specified choice.
*/
handleChange = (name) =>
{
const bubbleOnChange = (event, value) =>
(this.props.onChange?.(event, Object.keys(value).filter((choice) => (value[choice]))));
if (!this.#handlers.change[name])
{
this.#handlers.change[name] = (event) =>
{
const value = { ...this.state.value, [name]: !this.state.value[name] };
this.setState({ value }, () => (void bubbleOnChange(event, value)));
};
}
return this.#handlers.change[name];
}
/**
* Render the user interface for this component as a
* [Stack]{@link https://developer.microsoft.com/en-us/fluentui#/controls/web/stack} containing
* [Checkbox]{@link https://developer.microsoft.com/en-us/fluentui#/controls/web/checkbox} components.
* @function
* @public
* @returns {JSX} The user interface for this component.
*/
render()
{
const choices = this.props.Field.Choices.results;
return (<>
<Fabric.Stack {...this.props.stackTokens}>
{choices.map((choice) => (
<Fabric.Checkbox label={choice} checked={this.state.value[choice]}
onChange={this.handleChange(choice)} key={choice} />
))}
</Fabric.Stack>
<div
className="errorMessage"
id={`FormFieldDescription--${this.context.Item?.Id}__${this.props.FieldName}`}
role="alert"
style={{ display: this.props.errorMessage ? "" : "none" }}>
{this.props.errorMessage}
</div>
</>);
}
}
在表单通过 REST API 检索数据后,该组件使用该数据更新其状态。虽然正确更新了状态并且将正确的值传递给props
每个 Checkbox 组件,但 UI 具有误导性。例如,根据 Chrome DevTools 中的 React Components 检查器,checked
以下值分别设置为false
、true
、false
、false
和。false
显然,虽然props
设置正确,但用户会看到五个未选中的复选框。当用户单击本应选中的复选框时,state
会正确更新以反映所有五个复选框均未选中。这是用户单击第二个复选框后的样子。
用户与 Checkbox 组件交互,它们的行为与预期一样,但对于初始checked
属性设置为 的任何位置,底层值都完全反转true
。