使用 lodash debounce 设置 react js 中的状态
Using lodash debounce to set the state in react js
我知道这个问题已经被问过一千次了,但我不确定我是怎么做错的。我已经阅读了大量的 Whosebug,我正在寻找的是:我怎么做错了?
所以考虑下面的函数:
setFormValues(fieldName, value) {
let values = this.state.values;
values[fieldName] = value;
this.setState({
values: values
}, () => {
debounce(() => {
this.setState({
validationErrors: validator(this.state.validations, this.state.values),
});
}, 350);
});
}
每次元素值发生变化时,我们都会将其传递给表单(每个元素都是调用此函数的自身反应 class)。在表单中设置值(用于发布数据)后,我们必须验证表单的值。
让我们看一下验证器文件:
import React from 'react';
import moment from 'moment';
import startCase from 'lodash.startcase';
/**
* Validate the form values.
*/
export const validator = (validations, formValues) => {
let validationMessages = {};
for (const key in formValues) {
if (validations.hasOwnProperty(key)) {
const fieldValidations = validations[key];
const validationMessage = createValidationMessage(key, formValues[key], formValues, fieldValidations);
if (validationMessage !== null) {
validationMessages[key] = validationMessage;
}
}
}
return validationMessages;
}
/**
* Create a a validation message when the validatiom for the form value
* fails against the form field validations.
*/
const createValidationMessage = (fieldName, value, formValues, fieldValidations) => {
let message = null;
fieldValidations.forEach((fieldValidation) => {
if (fieldValidation.hasOwnProperty('cannot_percede_field')) {
if (formValues.hasOwnProperty(fieldValidation.cannot_percede_field)) {
const formValue = formValues[fieldValidation.cannot_percede_field];
if (isFutureDate(value, fieldValidation)) {
message = {
message: 'Date cannot be greator then today.',
isError: fieldValidation.show_error,
};
}
if (isDateLessThen(value, formValue, fieldValidation)) {
message = {
message: 'Date cannot be less then: ' + startCase(fieldValidation.cannot_percede_field) + '.',
isError: fieldValidation.show_error,
};
}
}
}
})
return message;
}
/**
* Is the date in question greator then date saved?
*/
const isFutureDate = (value, validationObject) => {
if (validationObject.cannot_be_future_date) {
return moment(value).isAfter(moment());
}
return false;
}
/**
* Is the date in question less then the date saved?
*/
const isDateLessThen = (value, formValue, validationObject) => {
if (validationObject.cannot_percede_field) {
return moment(value).isBefore(formValue);
}
return false;
}
我们所做的就是遍历值,检查验证器是否对该字段有特定规则。在这种情况下,如果字段声明它不能在另一个字段之前,我们会声明验证消息,如果有问题的字段比今天大,我们会再次设置消息。
有什么问题吗?
如果我取消去抖动,并将其保留为带有验证消息的设置状态,假设有的话,表单输入会非常慢。但是验证有效并且消息显示正确。
如果我让去抖动保持原样,并强制失败(例如,一个日期字段在另一个日期字段之前),什么也不会发生。
据我了解,问题可能是我一遍又一遍地调用 debounce,因此该函数永远不会触发。但是当你 select 一个日期时,它应该只调用一次,等待 350 毫秒,然后显示或不显示验证错误。
我在 lodashes debounce 上做错了什么以至于它甚至不起作用?
Debounce 采用一个函数,returns 一个包装原始函数的新函数。每当调用包装函数时,它都会在调用包装函数之前等待定义的时间。如果再次调用包装器,它会将包装函数的调用推迟指定的时间,依此类推。去抖通过维护一个内部计时器来工作。每当调用包装器时,计时器都会重置,只有当定义的时间过去而没有重置时,才会调用包装函数。
使用debounce时,需要一次定义函数,多次调用。如果你继续创建一个新函数(就像你在 setState 中需要的那样),该函数的内部计时器永远不会重置,因为你永远不会多次调用该函数。实际上,您得到一个延迟,然后调用包装函数,等等...
我会生成一个 validate
去抖函数,并在 setState
的回调中调用它:
validate = debounce(() => {
this.setState(({ validations, values }) => ({
validationErrors: validator(this.state.validations, this.state.values),
}))
})
setFormValues(fieldName, value) {
let values = this.state.values;
values[fieldName] = value;
this.setState({
values: values
}, this.validate;
});
}
为什么你的验证这么慢?
虽然我同意@skyboyer 不应该改变状态的观点,但这可能不是验证缓慢的原因。查看验证,很明显,对于每次击键,脚本 运行 都会对所有表单输入进行所有验证。其中两个验证使用 moment.js 来转换和比较日期,这非常慢。
因为您在 运行 setFormValues(fieldName, value)
时知道当前值,所以只验证该字段。此外,将 formValues[fieldValidation.cannot_percede_field]
和未来的数据通过 moment 转换一次,并缓存结果以供使用,而不是在每次击键时转换,或者放弃 moment 并使用原生 js,或更快的库。
"form input is slow" 的原因是你改变了 state
,对于 React 是 no-no。一旦你改变状态组件就不是 re-rendered。它仅在去抖动验证器调用 setState
.
时更新
虽然我同意 Ori Drori 的观点,但你可以将你的逻辑重塑为 运行 以不同的方式进行验证你肯定需要停止像
这样的状态变异
setFormValues(fieldName, value) {
let values = this.state.values;
// here you mutate this.state.values[fieldName]!
values[fieldName] = value;
this.setState({
values: values
}
并做
setFormValues(fieldName, value) {
this.setState(({values: oldValues}) => ({
values: {
...oldValues,
[fieldName]: value
}
}), ........
相反。
我知道这个问题已经被问过一千次了,但我不确定我是怎么做错的。我已经阅读了大量的 Whosebug,我正在寻找的是:我怎么做错了?
所以考虑下面的函数:
setFormValues(fieldName, value) {
let values = this.state.values;
values[fieldName] = value;
this.setState({
values: values
}, () => {
debounce(() => {
this.setState({
validationErrors: validator(this.state.validations, this.state.values),
});
}, 350);
});
}
每次元素值发生变化时,我们都会将其传递给表单(每个元素都是调用此函数的自身反应 class)。在表单中设置值(用于发布数据)后,我们必须验证表单的值。
让我们看一下验证器文件:
import React from 'react';
import moment from 'moment';
import startCase from 'lodash.startcase';
/**
* Validate the form values.
*/
export const validator = (validations, formValues) => {
let validationMessages = {};
for (const key in formValues) {
if (validations.hasOwnProperty(key)) {
const fieldValidations = validations[key];
const validationMessage = createValidationMessage(key, formValues[key], formValues, fieldValidations);
if (validationMessage !== null) {
validationMessages[key] = validationMessage;
}
}
}
return validationMessages;
}
/**
* Create a a validation message when the validatiom for the form value
* fails against the form field validations.
*/
const createValidationMessage = (fieldName, value, formValues, fieldValidations) => {
let message = null;
fieldValidations.forEach((fieldValidation) => {
if (fieldValidation.hasOwnProperty('cannot_percede_field')) {
if (formValues.hasOwnProperty(fieldValidation.cannot_percede_field)) {
const formValue = formValues[fieldValidation.cannot_percede_field];
if (isFutureDate(value, fieldValidation)) {
message = {
message: 'Date cannot be greator then today.',
isError: fieldValidation.show_error,
};
}
if (isDateLessThen(value, formValue, fieldValidation)) {
message = {
message: 'Date cannot be less then: ' + startCase(fieldValidation.cannot_percede_field) + '.',
isError: fieldValidation.show_error,
};
}
}
}
})
return message;
}
/**
* Is the date in question greator then date saved?
*/
const isFutureDate = (value, validationObject) => {
if (validationObject.cannot_be_future_date) {
return moment(value).isAfter(moment());
}
return false;
}
/**
* Is the date in question less then the date saved?
*/
const isDateLessThen = (value, formValue, validationObject) => {
if (validationObject.cannot_percede_field) {
return moment(value).isBefore(formValue);
}
return false;
}
我们所做的就是遍历值,检查验证器是否对该字段有特定规则。在这种情况下,如果字段声明它不能在另一个字段之前,我们会声明验证消息,如果有问题的字段比今天大,我们会再次设置消息。
有什么问题吗?
如果我取消去抖动,并将其保留为带有验证消息的设置状态,假设有的话,表单输入会非常慢。但是验证有效并且消息显示正确。
如果我让去抖动保持原样,并强制失败(例如,一个日期字段在另一个日期字段之前),什么也不会发生。
据我了解,问题可能是我一遍又一遍地调用 debounce,因此该函数永远不会触发。但是当你 select 一个日期时,它应该只调用一次,等待 350 毫秒,然后显示或不显示验证错误。
我在 lodashes debounce 上做错了什么以至于它甚至不起作用?
Debounce 采用一个函数,returns 一个包装原始函数的新函数。每当调用包装函数时,它都会在调用包装函数之前等待定义的时间。如果再次调用包装器,它会将包装函数的调用推迟指定的时间,依此类推。去抖通过维护一个内部计时器来工作。每当调用包装器时,计时器都会重置,只有当定义的时间过去而没有重置时,才会调用包装函数。
使用debounce时,需要一次定义函数,多次调用。如果你继续创建一个新函数(就像你在 setState 中需要的那样),该函数的内部计时器永远不会重置,因为你永远不会多次调用该函数。实际上,您得到一个延迟,然后调用包装函数,等等...
我会生成一个 validate
去抖函数,并在 setState
的回调中调用它:
validate = debounce(() => {
this.setState(({ validations, values }) => ({
validationErrors: validator(this.state.validations, this.state.values),
}))
})
setFormValues(fieldName, value) {
let values = this.state.values;
values[fieldName] = value;
this.setState({
values: values
}, this.validate;
});
}
为什么你的验证这么慢?
虽然我同意@skyboyer 不应该改变状态的观点,但这可能不是验证缓慢的原因。查看验证,很明显,对于每次击键,脚本 运行 都会对所有表单输入进行所有验证。其中两个验证使用 moment.js 来转换和比较日期,这非常慢。
因为您在 运行 setFormValues(fieldName, value)
时知道当前值,所以只验证该字段。此外,将 formValues[fieldValidation.cannot_percede_field]
和未来的数据通过 moment 转换一次,并缓存结果以供使用,而不是在每次击键时转换,或者放弃 moment 并使用原生 js,或更快的库。
"form input is slow" 的原因是你改变了 state
,对于 React 是 no-no。一旦你改变状态组件就不是 re-rendered。它仅在去抖动验证器调用 setState
.
虽然我同意 Ori Drori 的观点,但你可以将你的逻辑重塑为 运行 以不同的方式进行验证你肯定需要停止像
这样的状态变异setFormValues(fieldName, value) {
let values = this.state.values;
// here you mutate this.state.values[fieldName]!
values[fieldName] = value;
this.setState({
values: values
}
并做
setFormValues(fieldName, value) {
this.setState(({values: oldValues}) => ({
values: {
...oldValues,
[fieldName]: value
}
}), ........
相反。