Skip to content

Commit 2054671

Browse files
author
febobo
committed
add ref and computed
1 parent f345e30 commit 2054671

File tree

5 files changed

+682
-0
lines changed

5 files changed

+682
-0
lines changed

docs/global/index.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!--
2+
* @Author: your name
3+
* @Date: 2020-07-24 18:20:29
4+
* @LastEditTime: 2020-07-24 18:20:30
5+
* @LastEditors: Please set LastEditors
6+
* @Description: In User Settings Edit
7+
* @FilePath: /work/vue3-doc/docs/global/index.md
8+
-->

docs/reactivity/computed.md

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
### computed
2+
3+
> 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
4+
5+
```js
6+
const count = ref(1)
7+
const plusOne = computed(() => count.value + 1)
8+
9+
console.log(plusOne.value) // 2
10+
11+
plusOne.value++ // 错误!
12+
```
13+
14+
> 或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态。
15+
```js
16+
const count = ref(1)
17+
const plusOne = computed({
18+
get: () => count.value + 1,
19+
set: (val) => {
20+
count.value = val - 1
21+
},
22+
})
23+
24+
plusOne.value = 1
25+
console.log(count.value) // 0
26+
```
27+
28+
更多文档: [https://vue3js.cn/vue-composition-api/#computed](https://vue3js.cn/vue-composition-api/#computed)
29+
30+
### 正文
31+
32+
计算属性,可能会依赖其他 `reactive` 的值,同时会延迟和缓存计算值
33+
34+
```js
35+
export function computed<T>(
36+
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
37+
) {
38+
let getter: ComputedGetter<T>
39+
let setter: ComputedSetter<T>
40+
41+
// 如果传入是 function 说明是只读 computed
42+
if (isFunction(getterOrOptions)) {
43+
getter = getterOrOptions
44+
setter = __DEV__
45+
? () => {
46+
console.warn('Write operation failed: computed value is readonly')
47+
}
48+
: NOOP
49+
} else {
50+
// 不是方法说明是自定义的 getter setter
51+
getter = getterOrOptions.get
52+
setter = getterOrOptions.set
53+
}
54+
55+
let dirty = true
56+
let value: T
57+
let computed: ComputedRef<T>
58+
59+
// 创建 effect, 我们在看 effect 源码时知道了传入 lazy 代表不会立即执行,computed 表明 computed 上游依赖改变的时候,会优先 trigger runner effect, scheduler 表示 effect trigger 的时候会调用 scheduler 而不是直接调用 effect
60+
const runner = effect(getter, {
61+
lazy: true,
62+
// mark effect as computed so that it gets priority during trigger
63+
computed: true,
64+
scheduler: () => {
65+
// 在触发更新时把dirty置为true, 不会立即更新
66+
if (!dirty) {
67+
dirty = true
68+
trigger(computed, TriggerOpTypes.SET, 'value')
69+
}
70+
}
71+
})
72+
73+
// 构造一个 computed 返回
74+
computed = {
75+
__v_isRef: true,
76+
// expose effect so computed can be stopped
77+
effect: runner,
78+
get value() {
79+
// dirty为ture, get操作时,执行effect获取最新值
80+
//
81+
if (dirty) {
82+
value = runner()
83+
dirty = false
84+
}
85+
// dirty为false, 表示值未更新,直接返回
86+
track(computed, TrackOpTypes.GET, 'value')
87+
return value
88+
},
89+
set value(newValue: T) {
90+
setter(newValue)
91+
}
92+
} as any
93+
return computed
94+
}
95+
```

docs/reactivity/computed.spec.md

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
### computed.spec
2+
3+
> 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。
4+
5+
```js
6+
const count = ref(1)
7+
const plusOne = computed(() => count.value + 1)
8+
9+
console.log(plusOne.value) // 2
10+
11+
plusOne.value++ // 错误!
12+
```
13+
14+
> 或者传入一个拥有 get 和 set 函数的对象,创建一个可手动修改的计算状态。
15+
```js
16+
const count = ref(1)
17+
const plusOne = computed({
18+
get: () => count.value + 1,
19+
set: (val) => {
20+
count.value = val - 1
21+
},
22+
})
23+
24+
plusOne.value = 1
25+
console.log(count.value) // 0
26+
```
27+
28+
更多文档: [https://vue3js.cn/vue-composition-api/#computed](https://vue3js.cn/vue-composition-api/#computed)
29+
30+
### 正文
31+
32+
1. 每次返回的是最新的值
33+
```js
34+
it('should return updated value', () => {
35+
const value = reactive<{ foo?: number }>({})
36+
const cValue = computed(() => value.foo)
37+
expect(cValue.value).toBe(undefined)
38+
value.foo = 1
39+
expect(cValue.value).toBe(1)
40+
})
41+
```
42+
43+
2. 计算属性默认是 `lazy` 不会立即执行, 取的值未发生变化不会执行
44+
```js
45+
it('should compute lazily', () => {
46+
const value = reactive<{ foo?: number }>({})
47+
const getter = jest.fn(() => value.foo)
48+
const cValue = computed(getter)
49+
50+
// lazy
51+
expect(getter).not.toHaveBeenCalled()
52+
53+
expect(cValue.value).toBe(undefined)
54+
expect(getter).toHaveBeenCalledTimes(1)
55+
56+
// should not compute again
57+
cValue.value
58+
expect(getter).toHaveBeenCalledTimes(1)
59+
60+
// should not compute until needed
61+
value.foo = 1
62+
expect(getter).toHaveBeenCalledTimes(1)
63+
64+
// now it should compute
65+
expect(cValue.value).toBe(1)
66+
expect(getter).toHaveBeenCalledTimes(2)
67+
68+
// should not compute again
69+
cValue.value
70+
expect(getter).toHaveBeenCalledTimes(2)
71+
})
72+
```
73+
74+
3. 如果有`effect`是依赖 `computed` 结果的,当它改变时,`effect` 也会执行
75+
```js
76+
it('should trigger effect', () => {
77+
const value = reactive<{ foo?: number }>({})
78+
const cValue = computed(() => value.foo)
79+
let dummy
80+
effect(() => {
81+
dummy = cValue.value
82+
})
83+
expect(dummy).toBe(undefined)
84+
value.foo = 1
85+
expect(dummy).toBe(1)
86+
})
87+
```
88+
89+
4. `computed` 之间可以相互依赖
90+
```js
91+
it('should work when chained', () => {
92+
const value = reactive({ foo: 0 })
93+
const c1 = computed(() => value.foo)
94+
const c2 = computed(() => c1.value + 1)
95+
expect(c2.value).toBe(1)
96+
expect(c1.value).toBe(0)
97+
value.foo++
98+
expect(c2.value).toBe(2)
99+
expect(c1.value).toBe(1)
100+
})
101+
```
102+
103+
5. 参照3,4条
104+
```js
105+
it('should trigger effect when chained', () => {
106+
const value = reactive({ foo: 0 })
107+
const getter1 = jest.fn(() => value.foo)
108+
const getter2 = jest.fn(() => {
109+
return c1.value + 1
110+
})
111+
const c1 = computed(getter1)
112+
const c2 = computed(getter2)
113+
114+
let dummy
115+
effect(() => {
116+
dummy = c2.value
117+
})
118+
expect(dummy).toBe(1)
119+
expect(getter1).toHaveBeenCalledTimes(1)
120+
expect(getter2).toHaveBeenCalledTimes(1)
121+
value.foo++
122+
expect(dummy).toBe(2)
123+
// should not result in duplicate calls
124+
expect(getter1).toHaveBeenCalledTimes(2)
125+
expect(getter2).toHaveBeenCalledTimes(2)
126+
})
127+
128+
it('should trigger effect when chained (mixed invocations)', () => {
129+
const value = reactive({ foo: 0 })
130+
const getter1 = jest.fn(() => value.foo)
131+
const getter2 = jest.fn(() => {
132+
return c1.value + 1
133+
})
134+
const c1 = computed(getter1)
135+
const c2 = computed(getter2)
136+
137+
let dummy
138+
effect(() => {
139+
dummy = c1.value + c2.value
140+
})
141+
expect(dummy).toBe(1)
142+
143+
expect(getter1).toHaveBeenCalledTimes(1)
144+
expect(getter2).toHaveBeenCalledTimes(1)
145+
value.foo++
146+
expect(dummy).toBe(3)
147+
// should not result in duplicate calls
148+
expect(getter1).toHaveBeenCalledTimes(2)
149+
expect(getter2).toHaveBeenCalledTimes(2)
150+
})
151+
```
152+
153+
6. `computed` 可以 `stop`, `stop` 后不再响应
154+
```js
155+
it('should no longer update when stopped', () => {
156+
const value = reactive<{ foo?: number }>({})
157+
const cValue = computed(() => value.foo)
158+
let dummy
159+
effect(() => {
160+
dummy = cValue.value
161+
})
162+
expect(dummy).toBe(undefined)
163+
value.foo = 1
164+
expect(dummy).toBe(1)
165+
stop(cValue.effect)
166+
value.foo = 2
167+
expect(dummy).toBe(1)
168+
})
169+
```
170+
171+
7. 支持自定义 `setter` , `setter` 会触发 `effect`
172+
```js
173+
it('should support setter', () => {
174+
const n = ref(1)
175+
const plusOne = computed({
176+
get: () => n.value + 1,
177+
set: val => {
178+
n.value = val - 1
179+
}
180+
})
181+
182+
expect(plusOne.value).toBe(2)
183+
n.value++
184+
expect(plusOne.value).toBe(3)
185+
186+
plusOne.value = 0
187+
expect(n.value).toBe(-1)
188+
})
189+
190+
it('should trigger effect w/ setter', () => {
191+
const n = ref(1)
192+
const plusOne = computed({
193+
get: () => n.value + 1,
194+
set: val => {
195+
n.value = val - 1
196+
}
197+
})
198+
199+
let dummy
200+
effect(() => {
201+
dummy = n.value
202+
})
203+
expect(dummy).toBe(1)
204+
205+
plusOne.value = 0
206+
expect(dummy).toBe(-1)
207+
})
208+
```
209+
210+
8. 默认是只读对象,修改会抛出错误
211+
```js
212+
it('should warn if trying to set a readonly computed', () => {
213+
const n = ref(1)
214+
const plusOne = computed(() => n.value + 1)
215+
;(plusOne as WritableComputedRef<number>).value++ // Type cast to prevent TS from preventing the error
216+
217+
expect(
218+
'Write operation failed: computed value is readonly'
219+
).toHaveBeenWarnedLast()
220+
})
221+
```

0 commit comments

Comments
 (0)