从头开始实现反应性

2025-06-07

从头开始实现反应性

响应式是许多 Web 界面的核心。它使编写健壮且交互性强的 Web 应用变得非常容易。虽然大多数框架都内置了响应式功能,但在纯 JavaScript 中,你总会有需要响应式的时候。所以,我将在这里向你展示如何在 JavaScript 中实现响应式。

等一下...反应性是什么?

有很多解释,目前为止最好的解释是这个。但在这里,我将向您展示一个更容易理解的代码示例。

假设你有这个:

let who = 'Siddharth';

document.querySelector('h1').innerText = who;
Enter fullscreen mode Exit fullscreen mode

稍后,您更改who

who = 'Somebody';
Enter fullscreen mode Exit fullscreen mode

但是 H1 中的内容直到我们document.querySelector('h1').innerText = who;再次调用才会改变。这就是响应式的作用所在。document.querySelector('h1').innerText = who;当引用的变量发生变化时,它会自动重新运行代码(在我们的例子中是)。因此,当我们更改变量时,更改会自动反映在代码中。

引擎

注意:为了让本教程简洁(且有趣!),我不会实现错误处理、对象以及所有那些枯燥的检查。本教程的下一部分(如果我写的话!)会详细介绍其中的一些内容。

首先,让我们构建一个我们需要做出反应的对象:

let data = {
    name: 'John Doe',
    age: 25
};
Enter fullscreen mode Exit fullscreen mode

使其具有反应性的一种方法是让 setter/getter 监听事件并对其做出反应。




关于setter /getter 的简要说明。gettersetter是当对象的属性被调用/设置时调用的函数。这里有一个简单的例子:
const obj = {
    data: [],
    get foo() {
        return this.data.join(', ');
    },
    set foo(val) {
        this.data.push(val);
    }
}

obj.foo = 1;
obj.foo = 2;
obj.foo = 3;

obj.foo; //=> 1, 2, 3
Enter fullscreen mode Exit fullscreen mode
在构建反应性时,Setter 和 Getter 非常有用

因此,我们需要将对象更改为如下形式:

let data = {
    name: 'John Doe',
    get name () {
        return this.name;
    },

    set name (val) {
        this.name = name;
        // TODO notify
    }
};
Enter fullscreen mode Exit fullscreen mode

使用它的代码如下所示:

const data = new Reactive({
    name: 'John Doe',
    age: 25
});

data.listen('name', val => console.log('name was changed to ' + val));

data.contents.name = 'Siddharth';
//=> name was changed to Siddharth
Enter fullscreen mode Exit fullscreen mode

那么,让我们首先构建这个Reactive类:

class Reactive {
    constructor(obj) {/* TODO */}
    listen(prop) {/* TODO */}
}
Enter fullscreen mode Exit fullscreen mode

构造函数非常简单,只需设置数据并开始观察:

constructor (obj) {
    this.contents = obj;
    this.listeners = {}; // Will be explained later
    this.makeReactive(obj);
}
Enter fullscreen mode Exit fullscreen mode

现在,我们将实现makeReactive

makeReactive(obj) {
    Object.keys(obj).forEach(prop => this.makePropReactive(obj, prop));
}
Enter fullscreen mode Exit fullscreen mode

现在,我们将实现makePropReactive

makePropReactive(obj, key) {
    let value = obj[key]; // Cache

    Object.defineProperty(obj, key, {
        get () {
            return value;
        },
        set (newValue) {
            value = newValue;
            this.notify(key);
        }
    });
}
Enter fullscreen mode Exit fullscreen mode

在这里,我们使用Object.defineProperty在对象上设置 getter。

接下来要做的是设置一个通知器和一个监听器。监听器非常简单:

listen(prop, handler) {
    if (!this.listeners[prop]) this.listeners[prop] = [];

    this.listeners[prop].push(handler);
}
Enter fullscreen mode Exit fullscreen mode

在这里,我们将对象上的监听器设置为数组中的值。

接下来通知:

notify(prop) {
    this.listeners[prop].forEach(listener => listener(this.contents[prop]));
}
Enter fullscreen mode Exit fullscreen mode

好了,就到这里!以下是完整代码:

class Reactive {
    constructor (obj) {
        this.contents = obj;
        this.listeners = {};
        this.makeReactive(obj);
    }

    makeReactive(obj) {
        Object.keys(obj).forEach(prop => this.makePropReactive(obj, prop));
    }

    makePropReactive(obj, key) {
        let value = obj[key];

        // Gotta be careful with this here
        const that = this;

        Object.defineProperty(obj, key, {
            get () {
                    return value;
            },
            set (newValue) {
                value = newValue;
                that.notify(key)
            }
        });
    }

    listen(prop, handler) {
        if (!this.listeners[prop]) this.listeners[prop] = [];

        this.listeners[prop].push(handler);
    }

    notify(prop) {
        this.listeners[prop].forEach(listener => listener(this.contents[prop]));
    }
}
Enter fullscreen mode Exit fullscreen mode

很简单,不是吗?下面是回复:

// Setup code class Reactive { constructor (obj) { this.contents = obj; this.listeners = {}; this.makeReactive(obj); } makeReactive(obj) { Object.keys(obj).forEach(prop => this.makePropReactive(obj, prop)); } makePropReactive(obj, key) { let value = obj[key]; // Gotta be careful with this here const that = this; Object.defineProperty(obj, key, { get () { return value; }, set (newValue) { value = newValue; that.notify(key) } }); } listen(prop, handler) { if (!this.listeners[prop]) this.listeners[prop] = []; this.listeners[prop].push(handler); } notify(prop) { this.listeners[prop].forEach(listener => listener(this.contents[prop])); } } const data = new Reactive({ foo: 'bar' }); data.listen('foo', (change) => console.log('Change: ' + change)); data.contents.foo = 'baz';

感谢阅读!在接下​​来的部分中,我们将进一步探讨如何改进此功能。

文章来源:https://dev.to/siddharthshyniben/implementing-reactivity-from-scratch-51op
PREV
让我们改进文本区域!
NEXT
解释:命令式编程与声明式编程