Forzar la ejecución de una función de propiedad calculada

80

Dada una propiedad calculada

vm.checkedValueCount = ko.computed(function(){
  var observables = getCurrentValues();  //an array of ko.observable[]
  return _.filter(observables, function(v) { return v() }).length;
});

supongamos que getCurrentValues ​​() puede devolver diferentes conjuntos de observables que se modifican en otra parte del código (y proviene de una estructura más compleja que un observableArray).

Necesito checkedValueCountactualizar cuando sea

  • una de sus dependencias cambia
  • getCurrentValues ​​() devuelve un conjunto diferente de observables.

El problema es que ko.computedparece memorizar el último valor devuelto y solo se actualiza cuando se actualiza una dependencia. Esto maneja el primer caso pero no el último.

Lo que estoy buscando es una forma de forzar la ejecución de checkValueCount. Algo que pueda usar como:

changeCurrentValues();
vm.checkeValueCount.recalculate();

En pocas palabras, dado que tengo

a = ko.computed(function() { return Math.random() })

¿Cómo puedo forzar la invocación a()dos veces para devolver diferentes valores?

George Mauer
fuente
Vea mi respuesta actualizada "reescrita".
Josh

Respuestas:

116

Me di cuenta de que mi primera respuesta no entendía su punto y no resolvería su problema.

El problema es que un calculado solo se reevaluará si hay algo observable que lo obligue a reevaluarse. No existe una forma nativa de forzar una reevaluación de un calculado.

Sin embargo, puede evitar esto con algunos piratas informáticos creando un valor observable ficticio y luego diciéndoles a sus suscriptores que ha cambiado.

(function() {

    var vm = function() {
        var $this = this;

        $this.dummy = ko.observable();

        $this.curDate = ko.computed(function() {
            $this.dummy();
            return new Date();
        });

        $this.recalcCurDate = function() {
            $this.dummy.notifySubscribers();
        };        
    };

    ko.applyBindings(new vm());

}());​

Aquí hay un violín que muestra este enfoque.

Josh
fuente
Ah, intenté esto pero debí haberlo enganchado mal. Esto se ve hacky pero bueno (y lo suficientemente bueno como para poder extender ko.computed para permitir que funcione
George Mauer
Como siempre, lo más simple es lo mejor.
Henry Rodríguez
1
Esto es útil cuando cambia una gran variedad de datos que no son observables y después de los cambios se debe mostrar una parte de los datos.
Marek Bar
1
Su uso de notifySubscribers () es bueno. Mejor que lo que estaba haciendo al construir un número aleatorio y establecerlo en el valor de dummy ()
efreed
9

Existe un método para forzar el recálculo de todos los observables dependiendo del suyo:

getCurrentValues.valueHasMutated()
Rustam
fuente
9
Computadoras no tienen este método
George Mauer
getCurrentValues(); //an array of ko.observable[]
Rustam
Oh ya veo. Está diciendo que busque un observable que sea una dependencia del calculado y llame a su valueHasMutatedmétodo. Esto no es realmente fundamentalmente diferente de la respuesta de Josh anterior, ¿verdad? De hecho, lo obliga a saber qué se hace referencia y saber que esas referencias son observables (y no calculadas).
George Mauer
2
No obstante, es bueno mencionar esta función, ya que sigue siendo una opción para los lectores, aunque tal vez alguien pueda actualizar la respuesta aceptada para señalar los casos en los que esta función se puede intercambiar con notifySubscribers (), y si hay alguna penalización o ventaja de cualquier manera.
Shaun Wilson
4

Esta respuesta es conceptualmente la misma que la que dio @josh, pero presentada como una envoltura más genérica. Nota: esta versión es para un cálculo "escribible".

Estoy usando TypeScript, así que incluí primero la definición ts.d. Así que ignore esta primera parte si no es relevante para usted.

interface KnockoutStatic
{
    notifyingWritableComputed<T>(options: KnockoutComputedDefine<T>, context ?: any): KnockoutComputed<T>;
}

Notificando-escribible-calculado

Un contenedor para una escritura observableque siempre hace que los suscriptores sean notificados, incluso si no se actualizaron observables como resultado de la writellamada.

Simplemente reemplácelo function<T> (options: KnockoutComputedDefine<T>, context)con function(options, context)si no usa Typecript.

ko.notifyingWritableComputed = function<T> (options: KnockoutComputedDefine<T>, context)
{
    var _notifyTrigger = ko.observable(0);
    var originalRead = options.read;
    var originalWrite = options.write;

    // intercept 'read' function provided in options
    options.read = () =>
    {
        // read the dummy observable, which if updated will 
        // force subscribers to receive the new value
        _notifyTrigger();   
        return originalRead();
    };

    // intercept 'write' function
    options.write = (v) =>
    {
        // run logic provided by user
        originalWrite(v);

        // force reevaluation of the notifyingWritableComputed
        // after we have called the original write logic
        _notifyTrigger(_notifyTrigger() + 1);
    };

    // just create computed as normal with all the standard parameters
    return ko.computed(options, context);
}

El caso de uso principal para esto es cuando está actualizando algo que de otra manera no desencadenaría un cambio en un observable que es 'visitado' por la readfunción.

Por ejemplo, estoy usando LocalStorage para establecer algunos valores, pero no hay ningún cambio en ningún observable para activar la reevaluación.

hasUserClickedFooButton = ko.notifyingWritableComputed(
{
    read: () => 
    {
        return LocalStorageHelper.getBoolValue('hasUserClickedFooButton');
    },
    write: (v) => 
    {
        LocalStorageHelper.setBoolValue('hasUserClickedFooButton', v);        
    }
});

Tenga en cuenta que todo lo que necesitaba era un cambio ko.computedde ko.notifyingWritableComputedy entonces todo se cuida solo.

Cuando llamo hasUserClickedFooButton(true), el observable 'ficticio' se incrementa, lo que obliga a los suscriptores (y sus suscriptores) a obtener el nuevo valor cuando se actualiza el valor en LocalStorage.

(Nota: puede pensar que el notify: 'always'extensor es una opción aquí, pero eso es algo diferente).


Existe una solución adicional para un observable calculado que solo se puede leer:

ko.forcibleComputed = function(readFunc, context, options) {
    var trigger = ko.observable().extend({notify:'always'}),
        target = ko.computed(function() {
            trigger();
            return readFunc.call(context);
        }, null, options);
    target.evaluateImmediate = function() {
        trigger.valueHasMutated();
    };
    return target;
};


myValue.evaluateImmediate();

Del comentario de @mbest https://github.com/knockout/knockout/issues/1019 .

Simon_Weaver
fuente
¿Qué quieres decir con que no tiene valor? ¿Funcionó antes de cambiar a esto? ¿Está utilizando actualizaciones diferidas (la configuración global)? Me acabo de dar cuenta de que podría haber un conflicto si espera poder actualizar el observable y luego usar inmediatamente el valor. Si es así, intente agregar ko.tasks.runEarly () a mi método.
Simon_Weaver
1

supongamos que getCurrentValues ​​() puede devolver diferentes conjuntos de observables que se modifican en otra parte del código

Supongo que getCurrentValues ​​() es una función. Si pudiera convertirlo en un calculado, su checkValueCount simplemente comenzaría a funcionar mágicamente.

¿Puede hacer que getCurrentValues ​​sea un cálculo en lugar de una función?

Judá Gabriel Himango
fuente
Estoy simplificando mucho mi escenario real, pero claro, digamos que puede ser (lo que no sería el caso si fuera necesario, por ejemplo, tomar parámetros); no veo cómo eso haría alguna diferencia. Dentro de getCurrentValues ​​(), estoy cortando otros datos para determinar qué nodos de una vista de árbol deben devolverse. el problema es que la función puede devolver diferentes valores observables y necesito el calculado para detectarlo. Básicamente, exactamente de lo que hablan los documentos con las dependencias dinámicas, pero con la complicación de que se agreguen sobre la marcha
George Mauer
Haría una diferencia si el "rebanar y cortar en cubitos" se basara en observables. Por ejemplo, digamos que su corte y corte son los nodos que tienen .IsChecked () == true. Luego, tendría un .currentValues ​​() calculado que evaluaría todos los nodos y devolvería los que tienen .IsChecked (). ¿Se pueden cortar y cortar en cubitos con propiedades observables?
Judah Gabriel Himango
Realmente no estoy seguro de lo que estás diciendo Judah, cada invocación no puede ser un ko observable en mi caso, pero lo más importante es que no veo cómo eso podría importar, el knockout no hace ninguna reflexión sobre qué cierres ha llamado (no lo he comprobado, pero a menos que javascript haya entrado completamente en lisp-land, esto es imposible). Todo lo que puede hacer el cálculo es, en el mejor de los casos, rastrear cualquier observable que se llame.
George Mauer
-1

Dado que no hay una forma directa de forzar la actualización de un calculado, he creado un observable llamado toForceComputedUpdate, y lo llamé dentro de la función calculada para que el calculado escuche su actualización, luego para forzar la actualización lo llamo así para ForceComputedUpdate (Math .aleatorio)

yoel neuman
fuente