Skip to main content

8 posts tagged with "vue"

View All Tags

why use SFC in Vue

· One min read
Lex
单身狗本狗

Some users coming from a traditional web development background may have the concern that SFCs are mixing different concerns

IN the same place - which HTML/CSS/JS were supposed to separate!To answer this question, it is important for us to agree that separation of concerns is not equal to separation of file types. The ultimate

GoaL of engineering principles is to improve maintainability of codebases. Separation of concerns,

whEn applied

Dogmatically as separation of file types, does not help us reach that goal in the context of increasingly complex frontend applications.In

mOdern UI development, we have found that instead of dividing the codebase into three huge layers that interweave with one another, it makes much more sense to divide them into loosely-coupled components and compose them. Inside a component, its template, logic, and styles are inherently coupled, and collocating them actually makes the component more cohesive and maintainable.Note even if you don't like the idea of Single-File Components, you can still leverage its hot-reloading and pre-compilation features by separating your JavaScript and CSS into separate files

usinG Src Imports.

Why does the data property on a Vue component must be a function

· One min read
Lex
Front End Engineer @ Baoxiaohe

If you don’t get familiar with the basic rules of a framework (programming language, tool, etc) when starting to use it, things won’t work as expected, since it wasn’t conceived that way.

While using Vue for the first time, I made this by mistake:

data: {  message: "some message";}

then, I got the following warning message:

[Vue warn]: The “data” option should be a function that returns a per-instance value in component definitions.

What you should do instead is:

data: function() {  return {    message:'some message'  }}

So, the reason why Vue forces the data property to be a function is that each instance of a component should have its own data object. If we don’t do that, all instances will be sharing the same object and every time we change something, it will be reflected in all instances.

That's all✒

Vue 的响应式原理

· 3 min read
Lex
Front End Engineer @ Baoxiaohe

解析#

Vue 使用大篇幅介绍了他的响应式原理,So let's look how it work.

首先问两个问题:

  • data 对象中的数据发生改变,Vue 是如何得知的(watch)
  • 好的,假设 Vue 知道哪些数据 change 了,那怎么通知对应的 DOM 发生更新呢

我可真会提问题 🤷‍♂️

So,应该知道的是在 JavaScript 中有监听数据变化的 API Object.defineProperty 和 ES6 的 Proxy 了,

Object.defineProperty

事实上,Vue2 就是通过前者实现的,just like

const data = {  message: "可变数据",  date: "June 27,2021",};
Object.keys(data).forEach((key) => {  let value = data[key];
  Object.defineProperty(data, key, {    set(newValue) {      value = newValue;    },    get() {      return value;    },  });});

好的,第一个问题解决了。接下来要通知谁呢,换言之就是如何收集依赖,这个过程用到了发布/订阅者模式。 为了记录谁订阅了属性的改变,我们定义一个 Dep 类来管理依赖 ✍

// 发布者// Dep类管理依赖class Dep {  constructor() {    this.subscriptions = [];  }
  addSub(watcher) {    this.subscriptions.push(watcher);  }
  notify() {    this.subscriptions.forEach((item) => {      item.update();    });  }}
// 订阅者// 中介类Watcher负责将更新通知到其他地方class Watcher {  constructor(name) {    this.name = name;  }
  update() {    console.log(this.name + "已经update");  }}
// 封装数据监测Object.defineProperty(data, key, {  set(newValue) {    value = newValue;    dep.notify();  },  get() {    return value;  },});

Vue响应式原理的解析过程 这就是 Vue 响应式的大致原理。

实现#

通过 120 行 JavaScript 代码实现 Vue 的双向绑定 🎉🎉

class Vue {  constructor(options) {    this.$options = options;    this.$data = options.data;    this.$el = options.el;
    new Observer(this.$data);    // 代理this.$data的数据    Object.keys(this.$data).forEach((key) => this._proxy(key));    // 处理el挂载 传入Vue实例    new Compiler(this.$el, this);  }
  _proxy(key) {    Object.defineProperty(this, key, {      configurable: true,      enumerable: true,      set(newValue) {        this.$data[key] = newValue;      },      get() {        return this.$data[key];      },    });  }}
class Observer {  constructor(data) {    this.data = data;
    Object.keys(data).forEach((key) =>      this.defineReactive(this.data, key, data[key])    );  }
  defineReactive(data, key, val) {    // 为每个属性key创建一个Dep对象    const dep = new Dep();
    Object.defineProperty(data, key, {      enumerable: true,      configurable: true,      get() {        if (Dep.target) {          dep.addSub(Dep.target);        }        return val;      },      set(newValue) {        if (newValue === val) {          return;        }        val = newValue;        dep.notify();      },    });  }}
class Dep {  constructor() {    this.subs = [];  }
  addSub(sub) {    this.subs.push(sub);  }
  notify() {    this.subs.forEach((sub) => sub.update());  }}
const reg = /\{\{(.+)\}\}/;class Compiler {  constructor(el, vm) {    this.el = document.querySelector(el);    this.vm = vm;
    this.frag = this._createFragment();    this.el.appendChild(this.frag);  }
  _createFragment() {    const frag = document.createDocumentFragment();    let child;    while ((child = this.el.firstChild)) {      this._compile(child);      frag.appendChild(child);    }    return frag;  }
  _compile(node) {    if (node.nodeType === 1) {      const attrs = node.attributes;      if (attrs.hasOwnProperty("v-model")) {        const name = attrs["v-model"].nodeValue;        node.addEventListener("input", (e) => (this.vm[name] = e.target.value));      }    }    if (node.nodeType === 3) {      if (reg.test(node.nodeValue)) {        const name = RegExp.$1.trim();        new Watcher(node, name, this.vm);      }    }  }}
class Watcher {  constructor(node, name, vm) {    this.node = node;    this.name = name;    this.vm = vm;    Dep.target = this;    this.update();    Dep.target = null;  }
  update() {    this.node.nodeValue = this.vm[this.name];  }}

That's all🖼

TodoMVC powered by Vue.js

· 2 min read
Lex
Front End Engineer @ Baoxiaohe

Source#

// Full spec-compliant TodoMVC with localStorage persistence// and hash-based routing in ~120 effective lines of JavaScript.
// localStorage persistenceconst STORAGE_KEY = "todos-vuejs-2.0";const todoStorage = {  fetch() {    const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");    todos.forEach((todo, index) => {      todo.id = index;    });    todoStorage.uid = todos.length;    return todos;  },  save(todos) {    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));  },};
// visibility filtersconst filters = {  all(todos) {    return todos;  },  active(todos) {    return todos.filter((todo) => !todo.completed);  },  completed(todos) {    return todos.filter((todo) => todo.completed);  },};
// app Vue instanceconst app = Vue.createApp({  // app initial state  data() {    return {      todos: todoStorage.fetch(),      newTodo: "",      editedTodo: null,      visibility: "all",    };  },
  // watch todos change for localStorage persistence  watch: {    todos: {      handler(todos) {        todoStorage.save(todos);      },      deep: true,    },  },
  // computed properties  // http://vuejs.org/guide/computed.html  computed: {    filteredTodos() {      return filters[this.visibility](this.todos);    },    remaining() {      return filters.active(this.todos).length;    },    allDone: {      get() {        return this.remaining === 0;      },      set(value) {        this.todos.forEach((todo) => {          todo.completed = value;        });      },    },  },
  // methods that implement data logic.  // note there's no DOM manipulation here at all.  methods: {    pluralize(n) {      return n === 1 ? "item" : "items";    },    addTodo() {      var value = this.newTodo && this.newTodo.trim();      if (!value) {        return;      }      this.todos.push({        id: todoStorage.uid++,        title: value,        completed: false,      });      this.newTodo = "";    },
    removeTodo(todo) {      this.todos.splice(this.todos.indexOf(todo), 1);    },
    editTodo(todo) {      this.beforeEditCache = todo.title;      this.editedTodo = todo;    },
    doneEdit(todo) {      if (!this.editedTodo) {        return;      }      this.editedTodo = null;      todo.title = todo.title.trim();      if (!todo.title) {        this.removeTodo(todo);      }    },
    cancelEdit(todo) {      this.editedTodo = null;      todo.title = this.beforeEditCache;    },
    removeCompleted() {      this.todos = filters.active(this.todos);    },  },
  // a custom directive to wait for the DOM to be updated  // before focusing on the input field.  // http://vuejs.org/guide/custom-directive.html  directives: {    "todo-focus": {      updated(el, binding) {        if (binding.value) {          el.focus();        }      },    },  },});
// mountconst vm = app.mount(".todoapp");
// handle routingfunction onHashChange() {  const visibility = window.location.hash.replace(/#\/?/, "");  if (filters[visibility]) {    vm.visibility = visibility;  } else {    window.location.hash = "";    vm.visibility = "all";  }}
window.addEventListener("hashchange", onHashChange);onHashChange();

That's all

How to understand the Reactivity System in Vue

· 4 min read
Lex
Front End Engineer @ Baoxiaohe

As we all know,MVVM is an acronym of Model,View and ViewModel.

What's MVVM#

早先前端页面只是用来展示文本,与用户的交互很少。对于一些需要操作 DOM 的的需求使用 JQuery 这个解决方案就足够了。但随着需求的增多和复杂化,频繁操作 DOM 显得很不优雅。 MVVM MVVM 使数据和视图分开,Model 显示数据,View 显示 DOM 视图。View Model 负责监听数据的变化并将其渲染到 DOM,同时 DOM 的变化也会映射到 Model 层。

Reactivity System#

Vue realize reactive by data binding.That means you can only mudify data,than Vue will update views without handle DOM.Actually Vue will update node by virtual DOM,it faster than actual DOM.Different from React,Vue is by Object.defineProperty API and Observer design mode.

One of Vue’s most distinct features is the unobtrusive reactivity system. Models are proxied JavaScript objects. When you modify them, the view updates. It makes state management simple and intuitive, but it’s also important to understand how it works to avoid some common gotchas.

响应式原理

When you pass a normal JavaScript Object into a Vue instance as a data option, the Vue will walk through all the property of the Object and use Object.defineProperty to convert them all to getter/setter.Object.defineProperty is a non-shim feature in ES5, which is why the Vue doesn't support IE8 and lower browsers.The getter/setter is not visible to the user, but inside they make Vue could tracking dependencies, when the property is accessed and modified notice changes. To note here is that the different browsers in the console print data object format is different with getter/setter, so suggest install vue-devtools to obtain more user-friendly interface to check the data.

Each component instance map to a watcher instance, it will contact in the process of the component rendering the passed data's property is recorded as a dependency. Then when the setter of the dependency fires, it notifies Watcher, causing its associated component to rerender.

简单的说就是 Vue 会把data对象中所有propertyreactive 化,也就是加上 setter 和 getter,而这对用户是不可见的。

Object.defineProperty#

What is Object.defineProperty API,it play what role?For example,we define a variate b from obj like that:

let aValue;let obj = {};Object.defineProperty(obj, "b", {  get() {    console.log("监听正在获取a");    return aValue;  },  set(newValue) {    console.log("监听正在设置a");    aValue = newValue;  },  enumerable: true,  configurable: true,});obj.b;obj.b = 40;

We would get result:

监听正在获取 a
监听正在设置 a

So we know,it trigger getter when we visit variate from an Object,and trigger setter when we set the variate.Then,we can realize a sample reactive demo like that:

    <input type="text" id="txt" /><br />    <span id="sp"></span>
    <script>      let obj = {},        txt = document.getElementById("txt"),        sp = document.getElementById("sp");
      Object.defineProperty(obj, "msg", {        get() {          return txt.value;        },        set(newValue) {          txt.value = newValue;          sp.innerText = newValue;        },      });
      txt.addEventListener("keyup", (event) => {        obj.msg = event.target.value;      });    </script>

In effect,that's the principle for change detection in Vue.js.我们可以封装一下代码简化一下传参。

function defineReactive(data, key, val) {  Object.defineProperty(data, key, {    enumerable: true,    configurable: true,    get() {      return val;    },    set(newValue) {      if (val === newValue) {        return;      }      val = newValue;    },  });}

变化侦测起到了追踪变化的作用,但是更重要的是要收集依赖,我们之所以要追踪变化,就是为了当数据的属性发生变化时,可以通知那些使用到该数据的地方不是吗?所以就需要先收集依赖,等到数据发生了变化,再把之前收集到的依赖循环触发一遍就好了。显然,这就是观察者模式需要处理的问题了。

Observe design mode#

Ok,we know what's Object.defineProperty now,how about Observer design mode?
这里举个很简单的栗子:
我们喜欢一个明星(such as Evan You)就会去关注他的动态,例如去微博上点关注。此时微博就充当了观察者,它接收 Evan 发送的微博,然后将它们发送给订阅了 Evan 微博的粉丝。当黑粉取消订阅后,微博当然也就不再发送 Evan 的微博动态给黑粉们了。

Observer

So,there has three functions:the first is regist() 用来订阅.The second is notify() 用来广播动态.The third is remove() 用来取消订阅。Now we can realize a simple Observer model:

class Observer {  constructor() {    // we need container collect regist imformation    this.dep = [];  }
  regist(fn) {    this.dep.push(fn);  }
  notify() {    this.dep.forEach((item) => item());  }}

Vue 的响应式原理#

  1. init 阶段:Vue 的 data 的property都会被reactive化,也就是加上setter/getter函数。
function defineReactive(obj: Object, key: string, ...) {    const dep = new Dep()
    Object.defineProperty(obj, key, {      enumerable: true,      configurable: true,      get: function reactiveGetter () {        ....        dep.depend()        return value        ....      },      set: function reactiveSetter (newVal) {        ...        val = newVal        dep.notify()        ...      }    })  }
  class Dep {      static target: ?Watcher;      subs: Array<Watcher>;
      depend () {        if (Dep.target) {          Dep.target.addDep(this)        }      }
      notify () {        const subs = this.subs.slice()        for (let i = 0, l = subs.length; i < l; i++) {          subs[i].update()        }      }

源码将收集依赖的容器dep[]封装到 Dep 类中,每个 data 的property都有一个 dep 对象。当 getter 调用的时候去 dep 里执行depend()收集依赖,当 setter 调用时去 dep 里执行notify()触发依赖。

  1. mount 阶段
  2. update 阶段

参考 知乎还是掘金上的文章,腾讯女大佬。后面东西之后再补充吧...