Apego de TypeScript y Knockout a ''este'' problema-¿Se necesita la función lambda?
knockout.js (8)
Aunque prefiero la solución de Markus, esto es lo que he usado antes para solucionar este problema:
public fixThis(_this, func) {
return function () {
return _this[func].apply(_this, arguments);
};
}
<a href="#" data-bind="click: fixThis($parent, ''deleteItem'')">Delete</a>
Tenga en cuenta que se pueden pasar argumentos adicionales al método agregándolos después del nombre del método:
fixThis($parent, ''deleteItem'', arg1, arg2);
He estado creando una función htmlHelper usando TypeScript y KnockoutJS para editar una lista de correos electrónicos.
La lista de correos electrónicos es Knockout ObservableArray llamada correos electrónicos , y tengo un enlace contra cada elemento para eliminarlos. Este es el fragmento de HTML:
<ul data-bind="foreach: emails" >
<li>
<a href="#" data-bind="click: $parent.deleteItem">Delete</a>
<span data-bind="text: $data"></span>
</li>
</ul>
El enlace de eliminación está obligado a $ parent.deleteItem, este es un método en el modelo de vista:
// remove item
public deleteItem(emailToDelete: string) {
// remove item from list
this.emails.remove(emailToDelete);
}
Todo esto funciona hasta que se ejecuta el método deleteItem. El "esto" en este método cuando se llama es el elemento en la matriz, y no el modelo de vista. Por lo tanto this.emails es una referencia nula y falla.
Sé que TypeScript admite la sintaxis de Lambda, pero no puedo encontrar la forma correcta de escribir esto (hay algunos ejemplos).
¿O hay un enfoque diferente que podría tomar?
Guantes personas! Solo une $ parent como esto:
<a href="#" data-bind="click: $parent.deleteItem.bind($parent)">Delete</a>
Me inspiró la respuesta de bind
y se me ocurrió esto, creo que es un poco más fácil de leer.
<a href="#" data-bind="click: function () {$parent.deleteItem()}">Delete</a>
Envuelva el método en una función lambda / anónima. No olvides el ().
Mi solución final es una clase base, que enlaza todas las funciones prototipo consigo mismo en el constructor. Al igual que la solución de Markus Jarderot.
class BaseClass {
constructor() {
for (var i in this) {
if (!this.hasOwnProperty(i) && typeof (this[i]) === ''function'' && i != ''constructor'') {
this[i] = this[i].bind(this);
}
}
}
}
Ventajas:
- Todas las subclases están forzadas a llamar a super constructor, que es el comportamiento que quería.
- Cuando se ejecuta el código de reenlace, solo hay funciones de prototipo en el objeto (las variables se agregan más adelante).
- Evita la creación de grandes funciones en cada objeto. Solo se crea una pequeña función de proxy por objeto cuando se llama bind.
- Mejor organización del código de clase al no poner funciones en el constructor.
- Cualquier función se puede utilizar como devolución de llamada, no es necesario cambiar el código cuando se llama a una función desde un evento.
- No tiene el riesgo de vincular funciones dos veces.
- Es mejor vincular la función solo una vez, en lugar de hacerlo en la vista cada vez que se ejecuta el enlace de clic / evento.
PD:
Aún necesitarás el enlace polyfill.
Estoy usando typesript 0.9.5
Puede obtener el cierre correcto para ''esto'' al declarar el cuerpo del método dentro del constructor de la clase
class VM {
public deleteItem: (emailToDelete: string) => void;
constructor() {
this.deleteItem = (emailToDelete: string) => {
// ''this'' will be pointing to ''this'' from constructor
// no matter from where this method will be called
this.emails.remove(emailToDelete);
}
}
}
ACTUALIZAR:
Parece que desde Typescript ver 0.9.1 puede lograr el mismo resultado utilizando los inicializadores de campo lambda:
class VM {
public deleteItem = (emailToDelete: string) => {
this.emails.remove(emailToDelete);
}
}
Usa data-bind algo como esto:
data-bind="click:$parent.deleteItem.bind($parent)"
Asignar this
a that
como se muestra a continuación
public deleteItem(itemToDelete)
{
var that = this;
// remove item from list
that.emails.remove(itemToDelete);
}
para agregar mis 2 centavos, también hay una manera sucia que aprovecha la variable _esta creada por el compilador de Typescript para mantener una referencia sobre esto:
public deleteItem(emailToDelete: string) {
var that = eval(''_this'');
// remove item from list
that.emails.remove(emailToDelete); // remove? in JS, really?
}
declare class Email { }
declare class ObservableArray {
remove(any): void;
}
class MyViewModel {
public emails : ObservableArray;
constructor() {
Rebind(this);
}
public deleteItem(emailToDelete: Email) {
this.emails.remove(emailToDelete);
}
}
function Rebind(obj : any)
{
var prototype = <Object>obj.constructor.prototype;
for (var name in prototype) {
if (!obj.hasOwnProperty(name)
&& typeof prototype[name] === "function") {
var method = <Function>prototype[name];
obj[name] = method.bind(obj);
}
}
}
Es posible que desee un relleno múltiple para Function.bind()
:
// Polyfill for Function.bind(). Slightly modified version of
// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
if (typeof Function.prototype.bind !== "function") {
Function.prototype.bind = function(oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = <any[]> Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP && oThis ? this: oThis, aArgs.concat());
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}