javascript - superior - react js ejemplos
Uso de mixins versus componentes para la reutilización de código en Facebook React (2)
Estoy comenzando a usar Facebook React en un proyecto de Backbone y hasta ahora todo va muy bien.
Sin embargo, noté cierta duplicación en mi código React.
Por ejemplo, tengo varios widgets similares a formularios con estados como INITIAL
, SENDING
y SENT
. Cuando se presiona un botón, el formulario debe validarse, se realiza una solicitud y luego se actualiza el estado. El estado se mantiene dentro de React this.state
por supuesto, junto con los valores de campo.
Si se tratara de vistas de Backbone, habría extraído una clase base llamada FormView
pero mi impresión fue que React no respalda ni admite la FormView
subclases para compartir la lógica de visualización (corríjame si estoy equivocado).
He visto dos enfoques para la reutilización de código en React:
- Mixins (como LinkedStateMixin que se envía con React);
- Componentes del contenedor (como react-infinite-scroll ).
¿Tengo razón en que Mixins y contenedores son preferibles a la herencia en React? ¿Es esta una decisión de diseño deliberada? ¿Tendría más sentido usar un mixin o un componente de contenedor para mi ejemplo de "widget de formulario" del segundo párrafo?
Aquí hay una esencia con FeedbackWidget
y JoinWidget
en su estado actual . Tienen una estructura similar, un método similar de beginSend
y ambos necesitarán algún tipo de soporte de validación (todavía no existe).
Actualización: esta respuesta está desactualizada. Aléjate de los mixins si puedes. ¡Te lo adverti!
Mixins están muertos. Larga vida composición
Al principio, traté de usar subcomponentes para esto y extraer FormWidget
y InputWidget
. Sin embargo, abandoné este enfoque a la mitad porque quería un mejor control sobre las input
generadas y su estado.
Dos artículos que me ayudaron más:
- Pensar en React me hizo darme cuenta de que en realidad no quiero componentes anidados para esto;
- Los componentes reutilizables tienen un buen ejemplo de mixin.
Resultó que solo necesitaba escribir dos mixins (diferentes): ValidationMixin
y FormMixin
.
Así es como los separé.
ValidaciónMixin
La combinación de validación agrega métodos de conveniencia para ejecutar sus funciones de validador en algunas de las propiedades de su estado y almacenar propiedades "erróneas" en una matriz state.errors
para que pueda resaltar los campos correspondientes.
Fuente ( gist )
define(function () {
''use strict'';
var _ = require(''underscore'');
var ValidationMixin = {
getInitialState: function () {
return {
errors: []
};
},
componentWillMount: function () {
this.assertValidatorsDefined();
},
assertValidatorsDefined: function () {
if (!this.validators) {
throw new Error(''ValidatorMixin requires this.validators to be defined on the component.'');
}
_.each(_.keys(this.validators), function (key) {
var validator = this.validators[key];
if (!_.has(this.state, key)) {
throw new Error(''Key "'' + key + ''" is defined in this.validators but not present in initial state.'');
}
if (!_.isFunction(validator)) {
throw new Error(''Validator for key "'' + key + ''" is not a function.'');
}
}, this);
},
hasError: function (key) {
return _.contains(this.state.errors, key);
},
resetError: function (key) {
this.setState({
''errors'': _.without(this.state.errors, key)
});
},
validate: function () {
var errors = _.filter(_.keys(this.validators), function (key) {
var validator = this.validators[key],
value = this.state[key];
return !validator(value);
}, this);
this.setState({
''errors'': errors
});
return _.isEmpty(errors);
}
};
return ValidationMixin;
});
Uso
ValidationMixin
tiene tres métodos: validate
, hasError
y resetError
.
Espera que la clase defina objetos validators
, similar a propTypes
:
var JoinWidget = React.createClass({
mixins: [React.addons.LinkedStateMixin, ValidationMixin, FormMixin],
validators: {
email: Misc.isValidEmail,
name: function (name) {
return name.length > 0;
}
},
// ...
});
Cuando el usuario presiona el botón de envío, invoco validate
. Una llamada para validate
ejecutará cada validador y poblará this.state.errors
con una matriz que contiene claves de las propiedades que fallaron la validación.
En mi método de render
, utilizo hasError
para generar una clase de CSS correcta para los campos. Cuando el usuario pone el foco dentro del campo, llamo a resetError
para eliminar el resaltado del error hasta la próxima validate
llamada.
renderInput: function (key, options) {
var classSet = {
''Form-control'': true,
''Form-control--error'': this.hasError(key)
};
return (
<input key={key}
type={options.type}
placeholder={options.placeholder}
className={React.addons.classSet(classSet)}
valueLink={this.linkState(key)}
onFocus={_.partial(this.resetError, key)} />
);
}
FormMixin
La forma mixin maneja el estado del formulario (editable, enviado, enviado). Puede usarlo para desactivar entradas y botones mientras se envía la solicitud, y para actualizar su vista de manera correspondiente cuando se envía.
Fuente ( gist )
define(function () {
''use strict'';
var _ = require(''underscore'');
var EDITABLE_STATE = ''editable'',
SUBMITTING_STATE = ''submitting'',
SUBMITTED_STATE = ''submitted'';
var FormMixin = {
getInitialState: function () {
return {
formState: EDITABLE_STATE
};
},
componentDidMount: function () {
if (!_.isFunction(this.sendRequest)) {
throw new Error(''To use FormMixin, you must implement sendRequest.'');
}
},
getFormState: function () {
return this.state.formState;
},
setFormState: function (formState) {
this.setState({
formState: formState
});
},
getFormError: function () {
return this.state.formError;
},
setFormError: function (formError) {
this.setState({
formError: formError
});
},
isFormEditable: function () {
return this.getFormState() === EDITABLE_STATE;
},
isFormSubmitting: function () {
return this.getFormState() === SUBMITTING_STATE;
},
isFormSubmitted: function () {
return this.getFormState() === SUBMITTED_STATE;
},
submitForm: function () {
if (!this.isFormEditable()) {
throw new Error(''Form can only be submitted when in editable state.'');
}
this.setFormState(SUBMITTING_STATE);
this.setFormError(undefined);
this.sendRequest()
.bind(this)
.then(function () {
this.setFormState(SUBMITTED_STATE);
})
.catch(function (err) {
this.setFormState(EDITABLE_STATE);
this.setFormError(err);
})
.done();
}
};
return FormMixin;
});
Uso
Espera que el componente proporcione un método: sendRequest
, que debería devolver una promesa Bluebird. (Es trivial modificarlo para que funcione con Q u otra biblioteca de promesas).
Proporciona métodos de conveniencia como isFormEditable
, isFormSubmitting
y isFormSubmitted
. También proporciona un método para iniciar la solicitud: submitForm
. Puede llamarlo desde el controlador onClick
los botones de formulario.
Estoy construyendo un SPA con React (en producción desde hace 1 año) y casi nunca uso mixins.
El único caso de uso que tengo actualmente para mixins es cuando desea compartir el comportamiento que usa los métodos de ciclo de vida de React ( componentDidMount
etc.). Este problema lo resuelven los componentes de orden superior que Dan Abramov habla en su enlace (o mediante el uso de herencia de clase ES6).
Los mixins también se usan a menudo en frameworks, para hacer que Framework API esté disponible para todos los componentes, usando la función de contexto "oculto" de React. Esto ya no será necesario con la herencia de clase ES6.
La mayoría de las otras veces, mixins se utilizan, pero realmente no se necesitan y podrían reemplazarse fácilmente con simples ayudantes.
Por ejemplo:
var WithLink = React.createClass({
mixins: [React.addons.LinkedStateMixin],
getInitialState: function() {
return {message: ''Hello!''};
},
render: function() {
return <input type="text" valueLink={this.linkState(''message'')} />;
}
});
Puede refactorizar fácilmente el código LinkedStateMixin
para que la sintaxis sea:
var WithLink = React.createClass({
getInitialState: function() {
return {message: ''Hello!''};
},
render: function() {
return <input type="text" valueLink={LinkState(this,''message'')} />;
}
});
¿Hay alguna gran diferencia?