Tiptap (y muchos otros editores de texto basados en ProseMirror ) tiene esta increíble API donde podemos encadenar comandos.
Por ejemplo :
editor.chain().focus().toggleItalic().run() Aquí, llamar a chain() devuelve todos los comandos encadenables que a su vez devuelven comandos encadenables y así es como la cadena continúa hasta que llamas a run() .
Después de llamar a run() , los comandos entre chain() y run() se ejecutan sincronizados.
Lo que es interesante (hasta donde llega mi conocimiento) es que esto no es una clase. Todo esto también se puede ampliar (incluidos los tipos) creando extensiones personalizadas para Tiptap. Vea esta extensión en negrita y sus tipos .
Fui a través de la fuente , pero no pude comprender mucho.
Realmente me gustó este patrón de diseño y me preguntaba cómo debería implementar esto en general...
Digamos, quería implementar una calculadora en este patrón. ¿Cómo puedo hacer que esta línea funcione?
calculator.chain().setValue(10).add(3).sub(12).run() // 1¡Gracias!
Hay muchas formas en las que podría implementar esto, aquí hay solo una con un objeto simple. Una encapsulación de clase probablemente sería una buena idea. Siempre que mantenga sus métodos contenidos en un solo objeto, puede seguir devolviéndolo y agregándolo a la cola de devolución de llamada, que eventualmente se ejecutará en .run() y romperá el bucle.
const calculator = { state: { value: 0 }, callbacks: [] as ((...args: any[]) => any)[], _exec: { setValue: (val: number) => (calculator.state.value = val), add: (val: number) => (calculator.state.value += val), sub: (val: number) => (calculator.state.value -= val), }, _queue: { setValue: (val: number) => { calculator.callbacks.push(() => calculator._exec.setValue(val)); return calculator._queue; }, add: (val: number) => { calculator.callbacks.push(() => calculator._exec.add(val)); return calculator._queue; }, sub: (val: number) => { calculator.callbacks.push(() => calculator._exec.sub(val)); return calculator._queue; }, run: () => { calculator.callbacks.forEach((cb) => cb()); calculator.callbacks = []; }, }, chain: () => calculator._queue, }; calculator.chain().setValue(10).add(3).sub(12).run(); console.log(calculator.state.value); // logs 1Solo usa una class :
class Calculator { constructor(value) { this.value = value; } run() { return this.value; } add(x) { return new Calculator(this.value + x); } sub(x) { return new Calculator(this.value - x); } log() { console.log(this.value); return this; } static withValue(v) { return new this(v); } } const calc = Calculator.withValue(10).add(3).log(); console.log(calc.sub(12).run()); console.log(calc.add(29).run()); Lo único que es importante para el encadenamiento de métodos es devolver un objeto con todos los métodos (aquí: una instancia de Calculator ) de cada uno de los métodos encadenables. También puede lograr lo mismo sin sintaxis de class :
function calculatorWithValue(v) { return { run() { return v; }, add(x) { return calculatorWithValue(v + x); }, sub(x) { return calculatorWithValue(v - x); }, log() { console.log(v); return this; }, }; } const calc = calculatorWithValue(10).add(3).log(); console.log(calc.sub(12).run()); console.log(calc.add(29).run()); Recomiendo mantener los objetos inmutables, para permitir la creación de múltiples cadenas independientes o la bifurcación de una cadena existente; vea los ejemplos anteriores, donde el segundo resultado es 10 + 3 + 29 , no 10 + 3 - 12 + 29 .
Si desea que la ejecución sea perezosa, también es fácil:
class Calculator { constructor(run) { this.run = run; } static withValue(v) { return new this(() => v); } add(x) { return new Calculator(() => this.run() + x); } sub(x) { return new Calculator(() => this.run() - x); } log() { return new Calculator(() => { const v = this.run(); console.log(v); return v; }); } } const calc = Calculator.withValue(10).add(3).log(); console.log(calc.sub(12).run()); console.log(calc.add(29).run()); Si desea que esto sea extensible con algún tipo de arquitectura de complemento, simplemente coloque más métodos en el prototipo o cree el objeto dinámicamente. Para TypeScript, usaría la combinación de declaraciones para modificar la interfaz de la Calculator con las firmas que proporciona su complemento.