跳到主要内容

React DOM select

一、作用

二、验证选择属性

/**
* Implements a <select> host component that allows optionally setting the
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a
* stringable. If `multiple` is true, the prop must be an array of stringables.
*
* If `value` is not supplied (or null/undefined), user actions that change the
* selected option will trigger updates to the rendered options.
*
* If it is supplied (and not null/undefined), the rendered options will not
* update in response to user actions. Instead, the `value` prop must change in
* order for the rendered options to update.
*
* If `defaultValue` is provided, any options with the supplied values will be
* selected.
*
* 实现一个 <select> 宿主组件,允许可选地设置 props `value` 和 `defaultValue`。
* 如果 `multiple` 为 false,该 prop 必须是可转换为字符串的值。如果 `multiple`
* 为 true,该 prop 必须是一个可转换为字符串值的数组。
*
* 如果未提供 `value`(或为 null/undefined),用户改变所选选项的操作将触发渲染选项的
* 更新。
*
* 如果提供了 `value`(且不为 null/undefined),渲染的选项将不会响应用户操作而更新。
* 相反,必须更改 `value` prop 才能更新渲染的选项。
*
* 如果提供了 `defaultValue`,任何具有提供值的选项将被选中。
*/

export function validateSelectProps(element: Element, props: Object) {
if (__DEV__) {
checkSelectPropTypes(props);
if (
props.value !== undefined &&
props.defaultValue !== undefined &&
!didWarnValueDefaultValue
) {
console.error(
'Select elements must be either controlled or uncontrolled ' +
'(specify either the value prop, or the defaultValue prop, but not ' +
'both). Decide between using a controlled or uncontrolled select ' +
'element and remove one of these props. More info: ' +
'https://react.dev/link/controlled-components',
);
didWarnValueDefaultValue = true;
}
}
}

三、初始化选择

export function initSelect(
element: Element,
value: ?string,
defaultValue: ?string,
multiple: ?boolean,
) {
const node: HTMLSelectElement = element as any;
node.multiple = !!multiple;
if (value != null) {
updateOptions(node, !!multiple, value, false);
} else if (defaultValue != null) {
updateOptions(node, !!multiple, defaultValue, true);
}
}

四、水合选择

备注
export function hydrateSelect(
element: Element,
value: ?string,
defaultValue: ?string,
multiple: ?boolean,
): void {
const node: HTMLSelectElement = element as any;
const options: HTMLOptionsCollection = node.options;

const propValue: any = value != null ? value : defaultValue;

let changed = false;

if (multiple) {
const selectedValues = propValue as ?Array<string>;
const selectedValue: { [string]: boolean } = {};
if (selectedValues != null) {
for (let i = 0; i < selectedValues.length; i++) {
// Prefix to avoid chaos with special keys.
// 前缀以避免与特殊键发生冲突。
selectedValue['$' + selectedValues[i]] = true;
}
}
for (let i = 0; i < options.length; i++) {
const expectedSelected = selectedValue.hasOwnProperty(
'$' + options[i].value,
);
if (options[i].selected !== expectedSelected) {
changed = true;
break;
}
}
} else {
let selectedValue =
propValue == null ? null : toString(getToStringValue(propValue));
for (let i = 0; i < options.length; i++) {
if (selectedValue == null && !options[i].disabled) {
// We expect the first non-disabled option to be selected if the selected is null.
// 如果选择项为 null,我们期望第一个未被禁用的选项被选中。
selectedValue = options[i].value;
}
const expectedSelected = options[i].value === selectedValue;
if (options[i].selected !== expectedSelected) {
changed = true;
break;
}
}
}
if (changed) {
// If the current selection is different than our initial that suggests that the user
// changed it before hydration. Queue a replay of the change event.
// 如果当前选择与初始选择不同,说明用户在页面加载前已更改它。将更改事件排入重放队列。
queueChangeEvent(node);
}
}

五、更新选择

export function updateSelect(
element: Element,
value: ?string,
defaultValue: ?string,
multiple: ?boolean,
wasMultiple: ?boolean,
) {
const node: HTMLSelectElement = element as any;

if (value != null) {
updateOptions(node, !!multiple, value, false);
} else if (!!wasMultiple !== !!multiple) {
// For simplicity, reapply `defaultValue` if `multiple` is toggled.
// 为了简单起见,如果切换了 `multiple`,则重新应用 `defaultValue`。
if (defaultValue != null) {
updateOptions(node, !!multiple, defaultValue, true);
} else {
// Revert the select back to its default unselected state.
// 将选择项恢复到默认未选择状态。
updateOptions(node, !!multiple, multiple ? [] : '', false);
}
}
}

六、恢复受控选择状态

export function restoreControlledSelectState(element: Element, props: Object) {
const node: HTMLSelectElement = element as any;
const value = props.value;

if (value != null) {
updateOptions(node, !!props.multiple, value, false);
}
}

七、常量

1. 值属性名称

const valuePropNames = ['value', 'defaultValue'];

八、变量

1. 已警告值默认值

let didWarnValueDefaultValue;

if (__DEV__) {
didWarnValueDefaultValue = false;
}

九、工具

1. 获取声明错误附录

备注
function getDeclarationErrorAddendum() {
const ownerName = getCurrentFiberOwnerNameInDevOrNull();
if (ownerName) {
return '\n\nCheck the render method of `' + ownerName + '`.';
}
return '';
}

2. 检查选择属性类型

备注
/**
* Validation function for `value` and `defaultValue`.
* 用于 `value` 和 `defaultValue` 的验证函数。
*/
function checkSelectPropTypes(props: any) {
if (__DEV__) {
for (let i = 0; i < valuePropNames.length; i++) {
const propName = valuePropNames[i];
if (props[propName] == null) {
continue;
}
const propNameIsArray = isArray(props[propName]);
if (props.multiple && !propNameIsArray) {
console.error(
'The `%s` prop supplied to <select> must be an array if ' +
'`multiple` is true.%s',
propName,
getDeclarationErrorAddendum(),
);
} else if (!props.multiple && propNameIsArray) {
console.error(
'The `%s` prop supplied to <select> must be a scalar ' +
'value if `multiple` is false.%s',
propName,
getDeclarationErrorAddendum(),
);
}
}
}
}

3. 更新选项

备注
function updateOptions(
node: HTMLSelectElement,
multiple: boolean,
propValue: any,
setDefaultSelected: boolean,
) {
const options: HTMLOptionsCollection = node.options;

if (multiple) {
const selectedValues = propValue as Array<string>;
const selectedValue: { [string]: boolean } = {};
for (let i = 0; i < selectedValues.length; i++) {
// Prefix to avoid chaos with special keys.
// 前缀以避免与特殊键发生冲突。
selectedValue['$' + selectedValues[i]] = true;
}
for (let i = 0; i < options.length; i++) {
const selected = selectedValue.hasOwnProperty('$' + options[i].value);
if (options[i].selected !== selected) {
options[i].selected = selected;
}
if (selected && setDefaultSelected) {
options[i].defaultSelected = true;
}
}
} else {
// Do not set `select.value` as exact behavior isn't consistent across all
// browsers for all cases.
// 不要设置 `select.value`,因为在所有浏览器的所有情况下,其行为并不一致。
const selectedValue = toString(getToStringValue(propValue));
let defaultSelected = null;
for (let i = 0; i < options.length; i++) {
if (options[i].value === selectedValue) {
options[i].selected = true;
if (setDefaultSelected) {
options[i].defaultSelected = true;
}
return;
}
if (defaultSelected === null && !options[i].disabled) {
defaultSelected = options[i];
}
}
if (defaultSelected !== null) {
defaultSelected.selected = true;
}
}
}