refactor state

This commit is contained in:
Evan You 2016-04-12 21:21:50 -04:00
parent df97df1c05
commit 91a04ad038
4 changed files with 160 additions and 212 deletions

View File

@ -1,12 +1,12 @@
import Watcher from '../observer/watcher'
import { h, patch } from '../vdom/index'
import { nextTick, query } from '../util/index'
import stateMixin from './internal/state'
import { initState, setData } from './state'
export default function Vue (options) {
this.$options = options
this._watchers = []
this._initState()
initState(this)
this._el = query(options.el)
this._el.innerHTML = ''
this._watcher = new Watcher(this, options.render, this._update)
@ -22,7 +22,20 @@ Vue.prototype._update = function (vtree) {
this._tree = vtree
}
Vue.prototype.$forceUpdate = function () {
this._watcher.run()
}
Object.defineProperty(Vue.prototype, '$data', {
get () {
return this._data
},
set (newData) {
if (newData !== this._data) {
setData(this, newData)
}
}
})
Vue.prototype.__h__ = h
Vue.nextTick = nextTick
stateMixin(Vue)

View File

View File

@ -1,208 +0,0 @@
import Watcher from '../../observer/watcher'
import Dep from '../../observer/dep'
import { observe } from '../../observer/index'
import {
warn,
hasOwn,
isReserved,
isPlainObject,
bind
} from '../../util/index'
export default function (Vue) {
/**
* Accessor for `$data` property, since setting $data
* requires observing the new object and updating
* proxied properties.
*/
Object.defineProperty(Vue.prototype, '$data', {
get () {
return this._data
},
set (newData) {
if (newData !== this._data) {
this._setData(newData)
}
}
})
/**
* Setup the scope of an instance, which contains:
* - observed data
* - computed properties
* - user methods
* - meta properties
*/
Vue.prototype._initState = function () {
this._initMethods()
this._initData()
this._initComputed()
}
/**
* Initialize the data.
*/
Vue.prototype._initData = function () {
var data = this.$options.data
data = this._data = typeof data === 'function'
? data()
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object.',
this
)
}
// proxy data on instance
var keys = Object.keys(data)
var i = keys.length
while (i--) {
this._proxy(keys[i])
}
// observe data
observe(data, this)
}
/**
* Swap the instance's $data. Called in $data's setter.
*
* @param {Object} newData
*/
Vue.prototype._setData = function (newData) {
newData = newData || {}
var oldData = this._data
this._data = newData
var keys, key, i
// unproxy keys not present in new data
keys = Object.keys(oldData)
i = keys.length
while (i--) {
key = keys[i]
if (!(key in newData)) {
this._unproxy(key)
}
}
// proxy keys not already proxied,
// and trigger change for changed values
keys = Object.keys(newData)
i = keys.length
while (i--) {
key = keys[i]
if (!hasOwn(this, key)) {
// new property
this._proxy(key)
}
}
oldData.__ob__.removeVm(this)
observe(newData, this)
this._digest()
}
/**
* Proxy a property, so that
* vm.prop === vm._data.prop
*
* @param {String} key
*/
Vue.prototype._proxy = function (key) {
if (!isReserved(key)) {
// need to store ref to self here
// because these getter/setters might
// be called by child scopes via
// prototype inheritance.
var self = this
Object.defineProperty(self, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return self._data[key]
},
set: function proxySetter (val) {
self._data[key] = val
}
})
}
}
/**
* Unproxy a property.
*
* @param {String} key
*/
Vue.prototype._unproxy = function (key) {
if (!isReserved(key)) {
delete this[key]
}
}
/**
* Setup computed properties. They are essentially
* special getter/setters
*/
function noop () {}
Vue.prototype._initComputed = function () {
var computed = this.$options.computed
if (computed) {
for (var key in computed) {
var userDef = computed[key]
var def = {
enumerable: true,
configurable: true
}
if (typeof userDef === 'function') {
def.get = makeComputedGetter(userDef, this)
def.set = noop
} else {
def.get = userDef.get
? userDef.cache !== false
? makeComputedGetter(userDef.get, this)
: bind(userDef.get, this)
: noop
def.set = userDef.set
? bind(userDef.set, this)
: noop
}
Object.defineProperty(this, key, def)
}
}
}
function makeComputedGetter (getter, owner) {
var watcher = new Watcher(owner, getter, null, {
lazy: true
})
return function computedGetter () {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
/**
* Setup instance methods. Methods must be bound to the
* instance since they might be passed down as a prop to
* child components.
*/
Vue.prototype._initMethods = function () {
var methods = this.$options.methods
if (methods) {
for (var key in methods) {
this[key] = bind(methods[key], this)
}
}
}
}

View File

@ -0,0 +1,143 @@
import Watcher from '../observer/watcher'
import Dep from '../observer/dep'
import { observe } from '../observer/index'
import {
warn,
hasOwn,
isReserved,
isPlainObject,
bind
} from '../util/index'
export function initState (vm) {
initData(vm)
initComputed(vm)
initMethods(vm)
}
function initData (vm) {
var data = vm.$options.data
data = vm._data = typeof data === 'function'
? data()
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object.',
vm
)
}
// proxy data on instance
var keys = Object.keys(data)
var i = keys.length
while (i--) {
proxy(vm, keys[i])
}
// observe data
observe(data, vm)
}
function noop () {}
function initComputed (vm) {
var computed = vm.$options.computed
if (computed) {
for (var key in computed) {
var userDef = computed[key]
var def = {
enumerable: true,
configurable: true
}
if (typeof userDef === 'function') {
def.get = makeComputedGetter(userDef, vm)
def.set = noop
} else {
def.get = userDef.get
? userDef.cache !== false
? makeComputedGetter(userDef.get, vm)
: bind(userDef.get, vm)
: noop
def.set = userDef.set
? bind(userDef.set, vm)
: noop
}
Object.defineProperty(vm, key, def)
}
}
}
function makeComputedGetter (getter, owner) {
var watcher = new Watcher(owner, getter, null, {
lazy: true
})
return function computedGetter () {
if (watcher.dirty) {
watcher.evaluate()
}
if (Dep.target) {
watcher.depend()
}
return watcher.value
}
}
function initMethods (vm) {
var methods = vm.$options.methods
if (methods) {
for (var key in methods) {
vm[key] = bind(methods[key], vm)
}
}
}
function proxy (vm, key) {
if (!isReserved(key)) {
Object.defineProperty(vm, key, {
configurable: true,
enumerable: true,
get: function proxyGetter () {
return vm._data[key]
},
set: function proxySetter (val) {
vm._data[key] = val
}
})
}
}
function unproxy (vm, key) {
if (!isReserved(key)) {
delete vm[key]
}
}
export function setData (vm, newData) {
newData = newData || {}
var oldData = vm._data
vm._data = newData
var keys, key, i
// unproxy keys not present in new data
keys = Object.keys(oldData)
i = keys.length
while (i--) {
key = keys[i]
if (!(key in newData)) {
unproxy(vm, key)
}
}
// proxy keys not already proxied,
// and trigger change for changed values
keys = Object.keys(newData)
i = keys.length
while (i--) {
key = keys[i]
if (!hasOwn(vm, key)) {
// new property
proxy(vm, key)
}
}
oldData.__ob__.removeVm(vm)
observe(newData, vm)
vm.$forceUpdate()
}