Skip to content

Commit

Permalink
finish reactive module
Browse files Browse the repository at this point in the history
  • Loading branch information
bulv0620 committed Jul 18, 2022
0 parents commit 2d0969d
Show file tree
Hide file tree
Showing 12 changed files with 6,287 additions and 0 deletions.
24 changes: 24 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
5,990 changes: 5,990 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "vue-lite",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack",
"dev": "webpack serve --open"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.73.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3"
}
}
Binary file added src/examples/favicon.ico
Binary file not shown.
13 changes: 13 additions & 0 deletions src/examples/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="ch">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-lite-dev</title>
<script src="./vue-lite.js"></script>
</head>
<body>
<h1>hello vue-lite</h1>
</body>
</html>
13 changes: 13 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ref } from "./reactive/ref";
import { effect } from "./reactive/effect";
import { computed } from "./reactive/computed";

const num = window.num = ref(0);
const c = window.c = computed({
get(){
return num.value * 2;
},
set(newVal){
num.value = newVal / 2;
}
})
45 changes: 45 additions & 0 deletions src/reactive/computed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { isFunction } from "../utils";
import { effect, track, trigger } from "./effect";

export function computed(getterOrOption) {
let getter, setter;
if(isFunction(getterOrOption)){
getter = getterOrOption;
setter = () => {
console.warn('computed is readonly');
}
}
else{
getter = getterOrOption.get;
setter = getterOrOption.set;
}
return new ComputedImpl(getter, setter);
}

class ComputedImpl {
constructor(getter, setter) {
this._value = undefined;
this._dirty = true;
this.setter = setter;
this.effect = effect(getter, {
lazy: true,
scheduler: () => {
this._dirty = true;
trigger(this, 'value');
}
}); // accept effectFn
}

get value() {
if (this._dirty) {
this._value = this.effect(); // first time
this._dirty = false;
track(this, 'value');
}
return this._value;
}

set value(newVal) {
this.setter(newVal);
}
}
64 changes: 64 additions & 0 deletions src/reactive/effect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
const effectFnStack = [];
let activeEffectFn = null;

export function effect(fn, options = {}) {
const effectFn = () => {
try {
effectFnStack.push(effectFn);
activeEffectFn = effectFn;
return fn()
} finally {
effectFnStack.pop();
activeEffectFn = effectFnStack[effectFnStack.length - 1];
}
}

if (!options.lazy) {
effectFn();
}

effectFn.scheduler = options.scheduler;

return effectFn;
}

// effectFn 存储
const effectMap = new WeakMap();

// effect 捕获器
export function track(target, property) {

if (!activeEffectFn) {
return
}

let depsMap = effectMap.get(target);
if (!depsMap) {
effectMap.set(target, (depsMap = new Map()));
}

let deps = depsMap.get(property);
if (!deps) {
depsMap.set(property, (deps = new Set()));
}

deps.add(activeEffectFn);
}

// effect 触发器
export function trigger(target, property) {
const depsMap = effectMap.get(target);
if (!depsMap) return;

const deps = depsMap.get(property);
if (!deps) return;

deps.forEach(effectFn => {
if (effectFn.scheduler) {
effectFn.scheduler();
}
else {
effectFn()
}
})
}
50 changes: 50 additions & 0 deletions src/reactive/reactive.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { isObject, hasChanged, isArray } from "../utils";
import { track, trigger } from "./effect";

const proxyMap = new WeakMap();

export function reactive(target){
if(!isObject(target)){
return target;
}

if(isReactive(target)){
return target;
}

if(proxyMap.has(target)){
return proxyMap.get(target);
}

const proxy = new Proxy(target, {
get(target, property){
if(property === '__isReactive'){
return true;
}
const res = Reflect.get(...arguments);
track(target, property);

return isObject(res) ? reactive(res) : res;
},

set(target, property, value){
const oldVal = target[property];
const oldLength = target.length;
const res = Reflect.set(...arguments);
if(hasChanged(oldVal, value)){
trigger(target, property);
}
if(isArray(target) && hasChanged(oldLength, target.length)){
trigger(target, 'length');
}
return res
}
})

proxyMap.set(target, proxy);
return proxy;
}

export function isReactive(target){
return !!(target && target.__isReactive);
}
37 changes: 37 additions & 0 deletions src/reactive/ref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { hasChanged, isObject } from "../utils";
import { track, trigger } from "./effect";
import { reactive } from "./reactive";

export function ref(value) {
if(isRef(value)){
return value;
}
return new RefImpl(value);
}

export function isRef(value) {
return !!(value && value.__isRef);
}

class RefImpl {
constructor(value) {
this.__isRef = true;
this._value = convert(value);
}

get value() {
track(this, 'value');
return this._value;
}

set value(newVal) {
if(hasChanged(newVal, this._value)){
this._value = convert(newVal);
trigger(this, 'value');
}
}
}

function convert(value) {
return isObject(value) ? reactive(value) : value;
}
15 changes: 15 additions & 0 deletions src/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export function isObject(target){
return typeof target === 'object' && target !== null;
}

export function hasChanged(oldVal, val){
return oldVal !== val && !(Number.isNaN(oldVal) && Number.isNaN(val));
}

export function isArray(target){
return Array.isArray(target);
}

export function isFunction(target){
return typeof target === 'function';
}
18 changes: 18 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const path = require('path');

module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'vue-lite.js',
path: path.resolve(__dirname, 'dist'),
clean: true
},
devServer:{
static:{
directory: path.join(__dirname, 'src/examples'),
},
compress: true,
port: 9000
}
}

0 comments on commit 2d0969d

Please sign in to comment.