Skip to content

Commit

Permalink
实现ref,isRef,unRef,proxyRefs,computed
Browse files Browse the repository at this point in the history
  • Loading branch information
jindy committed Jun 7, 2022
1 parent 0b29f82 commit 6df8018
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 20 deletions.
28 changes: 28 additions & 0 deletions src/reactivity/computed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ReactiveEffect } from "./effect"

class ComputedImpl {
private _getter: any
private _dirty: boolean = true
private _value: any
private _effect: ReactiveEffect
constructor(getter) {
this._getter = getter

this._effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
}
})
}
get value() {
if (this._dirty) {
this._dirty = false
this._value = this._effect.run()
}
return this._value
}
}

export function computed(getter) {
return new ComputedImpl(getter)
}
12 changes: 6 additions & 6 deletions src/reactivity/effect.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { extend } from "../shared"

class ReactiveEffect {
export class ReactiveEffect {
private _fn: any
deps = []
active = true
Expand Down Expand Up @@ -58,10 +58,10 @@ export function track(target, key) {
dep = new Set()
depsMap.set(key, dep)
}
trackEffect(dep)
trackEffects(dep)
}

export function trackEffect(dep) {
export function trackEffects(dep) {
if (dep.has(activeEffect)) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
Expand All @@ -70,10 +70,10 @@ export function trackEffect(dep) {
export function trigger(target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
triggerEffect(dep)
triggerEffects(dep)
}

export function triggerEffect(dep) {
export function triggerEffects(dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler()
Expand Down Expand Up @@ -106,5 +106,5 @@ export function stop(runner) {
}

export function isTracking() {
return shouldTrack && activeEffect != undefined
return shouldTrack && activeEffect !== undefined
}
47 changes: 40 additions & 7 deletions src/reactivity/ref.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,64 @@
import { hasChange } from "./../shared/index"
import { isTracking, trackEffect, triggerEffect } from "./effect"
import { hasChanged, isObject } from "./../shared/index"
import { isTracking, trackEffects, triggerEffects } from "./effect"
import { reactive } from "./reactive"

class RefImpl {
private _value: any
public dep
private _rawValue: any
public __v_isRef = true
constructor(value) {
this._value = value
this._rawValue = value
this._value = convert(value)
this.dep = new Set()
}
get value() {
trackRefValue(this)
return this._value
}
set value(newValue) {
if (hasChange(newValue, this._value)) {
this._value = newValue
triggerEffect(this.dep)
if (hasChanged(newValue, this._rawValue)) {
this._rawValue = newValue
this._value = convert(newValue)
triggerEffects(this.dep)
}
}
}

function convert(value) {
return isObject(value) ? reactive(value) : value
}

function trackRefValue(ref) {
if (isTracking()) {
trackEffect(ref.dep)
trackEffects(ref.dep)
}
}

export function ref(value) {
return new RefImpl(value)
}

export function isRef(ref) {
return !!ref.__v_isRef
}

// 判断ref 是否为 ref.value
export function unRef(ref) {
return isRef(ref) ? ref.value : ref
}

export function proxyRefs(objectWithRefs) {
return new Proxy(objectWithRefs, {
get(target, key) {
return unRef(Reflect.get(target, key))
},
set(target, key, value) {
if (isRef(target(key)) && !isRef(value)) {
return (target[key].value = value)
} else {
return Reflect.set(target, key, value)
}
},
})
}
42 changes: 42 additions & 0 deletions src/reactivity/tests/computed.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { reactive } from "../reactive"
import { computed } from "../computed"

describe("computed", () => {
it("happy path", () => {
const user = reactive({
age: 1,
})

const age = computed(() => {
return user.age
})

expect(age.value).toBe(1)
})

it("should computed lazily", () => {
const value = reactive({ foo: 1 })
const getter = jest.fn(() => {
return value.foo
})
const cValue = computed(getter)

// lazy
expect(getter).not.toHaveBeenCalled()

expect(cValue.value).toBe(1)
expect(getter).toHaveBeenCalledTimes(1)

cValue.value
expect(getter).toHaveBeenCalledTimes(1)

value.foo = 2
expect(getter).toHaveBeenCalledTimes(1)

expect(cValue.value).toBe(2)
expect(getter).toHaveBeenCalledTimes(2)

cValue.value
expect(getter).toHaveBeenCalledTimes(2)
})
})
48 changes: 43 additions & 5 deletions src/reactivity/tests/ref.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { effect } from "../effect"
import { ref } from "../ref"
import { reactive } from "../reactive"
import { ref, isRef, unRef, proxyRefs } from "../ref"

describe("ref", () => {
it("happy path", () => {
Expand All @@ -22,12 +23,12 @@ describe("ref", () => {
expect(calls).toBe(2)
expect(dummy).toBe(2)

// a.value = 2
// expect(calls).toBe(2)
// expect(dummy).toBe(2)
a.value = 2
expect(calls).toBe(2)
expect(dummy).toBe(2)
})

it.skip("should make nested properties reactive", () => {
it("should make nested properties reactive", () => {
const a = ref({ count: 1 })
let dummy
effect(() => {
Expand All @@ -37,4 +38,41 @@ describe("ref", () => {
a.value.count = 2
expect(dummy).toBe(2)
})

it("isRef", () => {
const a = ref(1)
const user = reactive({
age: 10,
})
expect(isRef(a)).toBe(true)
expect(isRef(1)).toBe(false)
expect(isRef(user)).toBe(false)
})

it("unRef", () => {
const a = ref(1)

expect(unRef(a)).toBe(1)
expect(unRef(1)).toBe(1)
})

it("proxyRefs", () => {
const user = {
age: ref(10),
name: "kobe",
}

const proxyUser = proxyRefs(user)
expect(user.age.value).toBe(10)
expect(proxyUser.age).toBe(10)
expect(proxyUser.name).toBe("kobe")

// proxyUser.age = 20
// expect(proxyUser.age).toBe(20)
// expect(user.age.value).toBe(20)

// proxyUser.age = ref(10)
// expect(proxyUser.age).toBe(10)
// expect(user.age.value).toBe(10)
})
})
4 changes: 2 additions & 2 deletions src/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ export const isObject = (value) => {
return value !== null && typeof value === "object"
}

export const hasChange = (oldVal, newVal) => {
return Object.is(oldVal, newVal)
export const hasChanged = (val, newVal) => {
return !Object.is(val, newVal)
}

0 comments on commit 6df8018

Please sign in to comment.