观察对象发生改变后需要通知监听者,所以还需要实现通知者Notify:
class Notify { constructor() { this.listeners = []; } sub(fn) { this.listeners.push(fn); return () => { const idx = this.listeners.indexOf(fn); if (idx === -1) return; this.listeners.splice(idx, 1); }; } pub() { this.listeners.forEach((fn) => fn()); } } 调整Observable前面的Observable太简单了,无法完成属性计算的需求,结合上面Watch Notify的来调整下Observable。
function Observable(data) { const protoListeners = Object.create(null); // 给观察数据的所有属性创建一个Notify each(data, (_, key) => { protoListeners[key] = new Notify(); }); return new Proxy(data, { get(target, key) { // 属性依赖计算 if (CurrentWatchDep.current) { const watcher = CurrentWatchDep.current; watcher.addDep(protoListener[key]); } return target[key]; }, set(target, key, value) { target[key] = value; if (protoListeners[key]) { // 通知所有监听者 protoListeners[key].pub(); } return true; }, }); }好了,观察者的创建和订阅都完成了,开始模拟Vue。
模拟Vuevue-toy 使用React来实现视图的渲染,所以render函数里如果使用JSX则需要引入React
准备既然已经实现了Observable和Watch,那我们就来实现基本原理的示例:
codesandbox示例
import Observable from "vue-toy/cjs/Observable"; import Watch from "vue-toy/cjs/Watch"; function mount(vnode) { console.log(vnode); } function update(vnode) { console.log(vnode); } const data = Observable({ msg: "hello vue toy!", counter: 1 }); function render() { return `render: ${this.counter} | ${this.msg}`; } new Watch(data, render, update); mount(render.call(data)); setInterval(() => data.counter++, 1000); // 在控制台可看到每秒的输出信息这时将mount update的实现换成vdom就可以完成一个基本的渲染。
但这还不够,我们需要抽象并封装成组件来用。
Component源码
这里的Component像是React的高阶函数HOC,使用示例:
const Hello = Component({ props: ["msg"], data() { return { counter: 1, }; }, render(h) { return h("h1", null, this.msg, this.counter); }, });大概实现如下,options 参考文章开头
function Component(options) { return class extends React.Component { // 省略若干... constructor(props) { super(props); // 省略若干... // 创建观察对象 this.$data = Observable({ ...propsData, ...methods, ...data }, computed); // 省略若干... // 计算render依赖并监听 this.$watcher = new Watch( this.$data, () => { return options.render.call(this, React.createElement); }, debounce((children) => { this.$children = children; this.forceUpdate(); }) ); this.$children = options.render.call(this, React.createElement); } shouldComponentUpdate(nextProps) { if ( !shallowequal( pick(this.props, options.props || []), pick(nextProps, options.props || []) ) ) { this.updateProps(nextProps); this.$children = options.render.call(this, React.createElement); return true; } return false; } // 生命周期关联 componentDidMount() { options.mounted?.call(this); } componentWillUnmount() { this.$watcher.clearDeps(); options.destroyed?.call(this); } componentDidUpdate() { options.updated?.call(this); } render() { return this.$children; } }; } 创建主函数 Vue最后创建入口函数Vue,实现代码如下:
export default function Vue(options) { const RootComponent = Component(options); let el; if (typeof el === "string") { el = document.querySelector(el); } const props = { ...options.propsData, $el: el, }; return ReactDOM.render(React.createElement(RootComponent, props), el); } Vue.component = Component;好了,Vue的基本实现完成了。