Skip to content

slot 插槽

具名插槽

image-20221106164526868

作用域插槽

image-20221106170550110

vuex 总结

  • 案例结构

模块 B 使用了 namespaced

js
// store.js
export default new Vuex.Store({
  getters: {
    stateLength: (state) => state.state.length,
  },
  state: {
    titleRoot: "我是rootState的值",
    state: ["等待审核", "审核通过", "审核拒绝"],
  },
  modules: { moduleA, moduleB },
});
// moduleA.js
export default {
  getters: {
    doubleCounterA: (state) => state.counterA * 2,
  },
  state: {
    titleA: "我是moduleA中的title的值",
    counterA: 10,
  },
  mutations: {
    increment(state, payload) {
      console.log("moduleA", state.counterA, payload);
      const { amount } = payload;
      state.counterA += amount;
    },
  },
  actions: {
    incrementAsync({ commit }) {
      commit("increment", 5);
    },
  },
};

// moduleB.js
export default {
  namespaced: true,
  getters: {
    doubleCounterB: (state) => state.counterB * 2,
  },
  state: {
    titleB: "我是moduleB中的title的值",
    counterB: 100,
  },
  mutations: {
    increment(state, payload) {
      console.log("moduleB", state.counterB, payload);
      const { amount } = payload;
      state.counterB += amount;
    },
  },
  actions: {
    incrementAsync({ commit }) {
      commit("increment", 5);
    },
  },
};

// 数组形式 =》 启用namespaced只能使用第二种方式 未启用 namespaced 的只能使用对象形式
// ...mapState(["titleRoot"]),
// ...mapState("moduleB", ["titleB"]),
// ...mapState({ titleA: (state) => state.moduleA.titleA }),

// 对象形式
...mapState({
    titleRoot: "titleRoot", // 不能简写,仔细思考
    titleA: (state) => state.moduleA.titleA,
    titleB: (state) => state.moduleB.titleB,
}),
// 数组形式=》 未启用namespaced 可以全局 启用namespaced 需要标注模块名
// ...mapGetters(["stateLength", "doubleCounterA"]),
// ...mapGetters("moduleB", ["doubleCounterB"]),
// 对象形式
...mapGetters({
    stateLength: "stateLength",
    doubleCounterA: "doubleCounterA",
    doubleCounterB: "moduleB/doubleCounterB",
}),
// 方法
...mapMutations({
  changeCounterA: { type: "increment", amount: 1 },
  // changeCounterB: { type: "moduleB/increment", amount: 1 },
}),

...mapMutations(["increment", "moduleB/increment"]),
// changeCounterA() {
//   // this.$store.commit({ type: "increment", amount: 1 });
//   this.$store.commit("increment", { amount: 1 });
// },
changeCounterB() {
  // this.$store.commit({ type: "moduleB/increment", amount: 1 });
  // this.$store.commit("moduleB/increment", { amount: 1 });
  this["moduleB/increment"]({ amount: 10 });
},

image-20211210161536814image-20211210161717985

state 是所有模块与根模块的数据集合,getters、mutations、actions 如果启用了 namespaced,则需要加模块名前缀

redux 与 vuex 对比

构建工具 Vite

依赖预构建

  • 开发环境下,vite 会找到相关依赖,通过调用 esbuild(对 js 语法进行处理的库) 将其它规范的代码转换为 esmodule 规范,放入当前目录下的 node_modules.vite\deps
  1. 解决不同的第三包会有不同的导出格式 。
  2. 解决对路径的处理可以直接使用统一路径 node_modules/.vite/deps/ 方便路径重写。
  3. 解决网络多包传输的性能问题(也是原生 esmodule 规范不敢支持 node_modules 的原因)。
js
// 不会生成 node_modules/.vite/deps/lodash-es.js 文件
export default {
  optimizeDeps: {
    exclude: ['lodash-es']
  }
};
  • 生产环境下,会使用 @rollup/plugin-commonjs 构建
js

React hooks

  • useState

  • useEffect

  • useMemo

    jsx
    // 未使用 useMemo
    function Example1() {
      const [count, setCount] = useState(1);
      const [value, setValue] = useState('hello');
    
      const getNum = () => {
        console.log('getNum reRender 浪费性能');
        return Array.from({ length: count * 100 }, (item, index) => index).reduce(
          (initialValue, item) => initialValue + item,
          0
        );
      };
      return (
        <div>
          <h3>总和为:{getNum()}</h3>
          <button onClick={() => setCount(value => value + 1)}>count+1</button>
          <h3>{value}</h3>
          <input type="text" value={value} onInput={e => setValue(e.target.value)} />
        </div>
      );
    }
    // 使用 useMemo => 依赖改变才重新运行 并且返回的是值,并不需要调用
    function Example2() {
      const [count, setCount] = useState(1);
      const [value, setValue] = useState('hello');
    
      const getNum = useMemo(() => {
        console.log('只需要渲染一次');
        return Array.from({ length: count * 1000 }, (item, index) => index).reduce(
          (initialValue, index) => initialValue + index,
          0
        );
      }, [count]);
      return (
        <div>
          <h3>总和为:{getNum}</h3>
          <button onClick={() => setCount(value => value + 1)}>count+1</button>
          <h3>{value}</h3>
          <input type="text" value={value} onInput={e => setValue(e.target.value)} />
        </div>
      );
    }
  • useContext

    jsx

reactive

js
const bucket = new WeakMap();

function cleanUp(effectFn) {
  for (let i = 0; i < effectFn.deps.length; i++) {
    const deps = effectFn.deps[i];
    deps.delete(effectFn);
  }
  effectFn.deps.length = 0;
}

function track(target, key) {
  if (activeEffect) {
    let depsMap = bucket.get(target);
    if (!depsMap) bucket.set(target, (depsMap = new Map()));
    let deps = depsMap.get(key);
    if (!deps) depsMap.set(key, (deps = new Set()));
    deps.add(activeEffect);
    activeEffect.deps.push(deps);
    // console.log('track=====> ', key);
  }
}

function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (depsMap) {
    const effects = depsMap.get(key);
    const effectsToRun = new Set();
    effects?.forEach(effect => {
      if (activeEffect !== effect) effectsToRun.add(effect);
    });
    effectsToRun.forEach(effect => {
      // console.log('trigger=====> ', key);
      if (effect.options?.scheduler) {
        effect.options.scheduler(effect);
      } else {
        effect();
      }
    });
  }
}

function reactive(target) {
  return new Proxy(target, {
    get(target, p, receiver) {
      track(target, p);
      return Reflect.get(target, p, receiver);
    },
    set(target, p, newValue, receiver) {
      const res = Reflect.set(target, p, newValue, receiver);
      trigger(target, p);
      return res;
    }
  });
}

let activeEffect;
const effectStack = [];
function effect(fn, options) {
  const effectFn = () => {
    cleanUp(effectFn);
    activeEffect = effectFn;
    effectStack.push(effectFn);
    const result = fn();
    effectStack.pop();
    activeEffect = effectStack.at(-1);
    return result;
  };
  effectFn.deps = [];
  effectFn.options = options;
  if (effectFn.options?.lazy) {
    return effectFn;
  }
  effectFn();
}

const obj = reactive({ ok: true, text: 'hello world', foo: 1, bar: 2, delay: 1000 });
// effect(() => {
//   document.body.innerText = obj.ok ? obj.text : 'not';
//   obj.foo++;
//   obj.noExist = 'noExist';
// });
// obj.ok = false;

// let temp1, temp2;
// effect(() => {
//   console.log('======== effectFn1 run ========');
//   effect(() => {
//     console.log('======== effectFn2 run ========');
//     temp2 = obj.text;
//   });
//   temp1 = obj.ok;
// });

// obj.text = 'hello vue';

function computed(getter) {
  let value,
    dirty = true;
  const effectFn = effect(getter, {
    lazy: true,
    scheduler(fn) {
      dirty = true;
      trigger(obj, value);
    }
  });
  const obj = {
    get value() {
      if (dirty) {
        value = effectFn();
        dirty = false;
      }
      track(obj, value);
      return value;
    }
  };
  return obj;
}

// const sumRes = computed(() => obj.foo + obj.bar);
// console.log(sumRes.value);
// console.log(sumRes.value);

// effect(() => {
//   console.log('sumRes.value=====> ', sumRes.value);
// });
// obj.foo = 3;

function travese(value, seen = new Set()) {
  if (typeof value !== 'object' || typeof value === null || seen.has(value)) return;
  seen.add(value);
  for (const k in value) {
    travese(value[k], seen);
  }
  return value;
}

function watch(source, cb, options) {
  let getter;
  if (typeof source === 'function') {
    getter = source;
  } else {
    getter = () => travese(source);
  }

  let newValue, oldValue, cleanUp;

  function onInvalidate(fn) {
    cleanUp = fn;
  }

  function job() {
    cleanUp?.();
    newValue = effectFn();
    cb(newValue, oldValue, onInvalidate);
    oldValue = newValue;
  }

  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      if (options?.flush === 'post') {
        Promise.resolve().then(job);
      } else {
        job();
      }
    }
  });

  if (options?.immediate) {
    job();
  } else {
    oldValue = effectFn();
  }
}

watch(
  obj,
  async (newValue, oldValue, onInvalidate) => {
    console.log(newValue.delay, oldValue.delay);
    let expired = false;
    onInvalidate(() => {
      expired = true;
    });
    const res = await fetch(`https://www.test.com/test?delay=${newValue.delay}`).then(res =>
      res.json()
    );

    if (!expired) {
      console.log(res);
    }
  },
  {
    // immediate: true
    // flush: 'post'
  }
);

obj.delay += 2000;
obj.delay += 2000;
// obj.foo++;

console.log('end');

入口文件查看 Vue 源码

  1. 查看package.json文件
"dev": "rollup -w -c scripts/config.js --environment TARGET:full-dev"
  1. 查看 scripts/config.js full-dev 通过代码分析出入口文件路径为 src/platforms/web/entry-runtime-with-compiler.ts
js
const aliases = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  server: resolve('packages/server-renderer/src'),
  sfc: resolve('packages/compiler-sfc/src')
}

const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

const full-dev = {
    entry: resolve('web/entry-runtime-with-compiler.ts'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
},

initGlobalAPIVue 添加静态方法

js
export function initGlobalAPI(Vue: GlobalAPI) {
  // config
  const configDef = {};
  configDef.get = () => config;
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      warn('Do not replace the Vue.config object, set individual fields instead.');
    };
  }
  // Vue.config
  Object.defineProperty(Vue, 'config', configDef);

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  };

  Vue.set = set;
  Vue.delete = del;
  Vue.nextTick = nextTick;

  Vue.options = Object.create(null);
  config._assetTypes.forEach(type => {
    // Vue.options 添加 components directives filters 属性
    Vue.options[type + 's'] = Object.create(null);
  });

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue;
  // Vue.options.components.KeepAlive
  extend(Vue.options.components, builtInComponents);
  // Vue => 静态方法 use Mixin Extend
  initUse(Vue);
  initMixin(Vue);
  initExtend(Vue);
  // Vue => 静态方法 component directive filter
  initAssetRegisters(Vue);
}
js
function Vue(options) {
  if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue)) {
    warn('Vue is a constructor and should be called with the `new` keyword');
  }
  this._init(options);
}
// 实例方法 __init
initMixin(Vue);
// 实例方法 $data $props $set $delete $watch
stateMixin(Vue);
// 实例方法 $on $once $off $emit
eventsMixin(Vue);
// 实例方法  _update(没写错)
lifecycleMixin(Vue);
// 实例方法 $nextTick _render _o _n _s _l _t _q _i _m _f _k _b _v _e _u
renderMixin(Vue);

export default Vue;

Released under the MIT License | Copyright © 2022-present HOUJIAN