注: 为了直观的看到 Vue3 的实现逻辑, 本文移除了边缘情况处理、兼容处理、DEV环境的特殊逻辑等, 只保留了核心逻辑

vue-next/reactivity 实现了 Vue3 的响应性, reactivity 提供了以下接口:

export {
ref, // 代理基本类型
shallowRef, // ref 的浅代理模式
isRef, // 判断一个值是否是 ref
toRef, // 把响应式对象的某个 key 转为 ref
toRefs, // 把响应式对象的所有 key 转为 ref
unref, // 返回 ref.value 属性
proxyRefs,
customRef, // 自行实现 ref
triggerRef, // 触发 customRef
Ref, // 类型声明
ToRefs, // 类型声明
UnwrapRef, // 类型声明
ShallowUnwrapRef, // 类型声明
RefUnwrapBailTypes // 类型声明
} from './ref'
export {
reactive, // 生成响应式对象
readonly, // 生成只读对象
isReactive, // 判断值是否是响应式对象
isReadonly, // 判断值是否是只读对象
isProxy, // 判断值是否是 proxy
shallowReactive, // 生成浅响应式对象
shallowReadonly, // 生成浅只读对象
markRaw, // 让数据不可被代理
toRaw, // 获取代理对象的原始对象
ReactiveFlags, // 类型声明
DeepReadonly // 类型声明
} from './reactive'
export {
computed, // 计算属性
ComputedRef, // 类型声明
WritableComputedRef, // 类型声明
WritableComputedOptions, // 类型声明
ComputedGetter, // 类型声明
ComputedSetter // 类型声明
} from './computed'
export {
effect, // 定义副作用函数, 返回 effect 本身, 称为 runner
stop, // 停止 runner
track, // 收集 effect 到 Vue3 内部的 targetMap 变量
trigger, // 执行 targetMap 变量存储的 effects
enableTracking, // 开始依赖收集
pauseTracking, // 停止依赖收集
resetTracking, // 重置依赖收集状态
ITERATE_KEY, // 固定参数
ReactiveEffect, // 类型声明
ReactiveEffectOptions, // 类型声明
DebuggerEvent // 类型声明
} from './effect'
export {
TrackOpTypes, // track 方法的 type 参数的枚举值
TriggerOpTypes // trigger 方法的 type 参数的枚举值
} from './operations'

一、名词解释

  • target: 普通的 JS 对象

  • reactive: @vue/reactivity 提供的函数, 接收一个对象, 并返回一个 代理对象, 即响应式对象

  • shallowReactive: @vue/reactivity 提供的函数, 用来定义浅响应对象

  • readonly:@vue/reactivity 提供的函数, 用来定义只读对象

  • shallowReadonly: @vue/reactivity 提供的函数, 用来定义浅只读对象

  • handlers: Proxy 对象暴露的钩子函数, 有 get()set()deleteProperty()ownKeys() 等, 可以参考MDN

  • targetMap: @vue/reactivity 内部变量, 存储了所有依赖

  • effect: @vue/reactivit 提供的函数, 用于定义副作用, effect(fn, options) 的参数就是副作用函数

  • watchEffect: @vue/runtime-core 提供的函数, 基于 effect 实现

  • track: @vue/reactivity 内部函数, 用于收集依赖

  • trigger: @vue/reactivity 内部函数, 用于消费依赖

  • scheduler: effect 的调度器, 允许用户自行实现

二、Vue3 实现响应式的思路

先看下边的流程简图, 图中 Vue 代码的功能是: 每隔一秒在 idBoxdiv 中输出当前时间

在开始梳理 Vue3 实现响应式的步骤之前, 要先简单理解 effect, effect 是响应式系统的核心, 而响应式系统又是 Vue3 的核心

上图中从 tracktargetMap 的黄色箭头, 和从 targetMaptrigger 的白色箭头, 就是 effect 函数要处理的环节

effect 函数的语法为:

effect(fn, options)

effect 接收两个参数, 第一个必填参数 fn 是副作用函数

第二个选填 options 的参数定义如下:

export interface ReactiveEffectOptions {
lazy?: boolean // 是否延迟触发 effect
scheduler?: (job: ReactiveEffect) => void // 调度函数
onTrack?: (event: DebuggerEvent) => void // 追踪时触发
onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发
onStop?: () => void // 停止监听时触发
allowRecurse?: boolean // 是否允许递归
}

下边从流程图中左上角的 Vue 代码开始

第 1 步

通过 reactive 方法将 target 对象转为响应式对象, reactive 方法的实现方法如下:

import { mutableHandlers } from './baseHandlers'
import { mutableCollectionHandlers } from './collectionHandlers' const reactiveMap = new WeakMap<Target, any>()
const readonlyMap = new WeakMap<Target, any>() export function reactive(target: object) {
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
} function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
const targetType = getTargetType(target) // 先忽略, 上边例子中, targetType 的值为: 1
const proxy = new Proxy(
target,
targetType === 2 ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}

reactive 方法携带 target 对象和 mutableHandlersmutableCollectionHandlers 调用 createReactiveObject 方法, 这两个 handers 先忽略

createReactiveObject 方法通过 reactiveMap 变量缓存了一份响应式对象, reactiveMapreadonlyMap 变量是文件内部的变量, 相当于文件级别的闭包变量

其中 targetType 有三种枚举值: 0 代表不合法, 1 代表普通对象, 2 代表集合, 图中例子中, targetType 的值为 1, 对于 { text: '' } 这个普通对象传进 reactive() 方法时, 使用 baseHandlers 提供的 mutableHandlers

最后调用 Proxy 方法将 target 转为响应式对象, 其中 "响应" 体现在 handers 里, 可以这样理解: reactive = Proxy (target, handlers)

第 2 步

mutableHandlers 负责挂载 getsetdeletePropertyhasownKeys 这五个方法到响应式对象上

其中 gethasownKeys 负责收集依赖, setdeleteProperty 负责消费依赖

响应式对象的 gethasownKeys 方法被触发时, 会调用 createGetter 方法, createGetter 的实现如下:

function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
const res = Reflect.get(target, key, receiver)
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}

{ text: '' } 这个普通JS对象传到 createGetter 时, key 的值为: text, res 的值为: String 类型, 如果 res 的值为 Object 类型则会递归调用, 将 res 转为响应式对象

createGetter 方法的目的是触发 track 方法, 对应本文的第 3 步

响应式对象的 setdeleteProperty 方法被触发时, 会调用 createSetter 方法, createSetter 的实现如下:

function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
const result = Reflect.set(target, key, value, receiver)
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
return result
}
}

createSetter 方法的目的是触发 trigger 方法, 对应本文的第 4 步

第 3 步

这一步是整个响应式系统最关键的一步, 即我们常说的依赖收集, 依赖收集的概念很简单, 就是把 响应式数据副作用函数 建立联系

文章一开始流程图的例子中, 就是把 target 对象和 document.getElementById("Box").innerText = date.text; 这个副作用函数建立关联, 这个 "关联" 指的就是上边提到的 targetMap 变量, 后边会详细描述一下 targetMap 对象的结构

第 2 步介绍了 createGetter 方法的核心是调用 track 方法, track 方法由 @/vue/reativity/src/effect.ts 提供, 下面看一下 track 的实现:

const targetMap = new WeakMap<any, KeyToDepMap>()

// target: { text: '' }
// type: get
// key: text
export function track(target: object, type: TrackOpTypes, key: unknown) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
}
}

track 方法我们能看到 targetMap 这个闭包变量上储存了所有的 effect, 换句话说是把能影响到 target 的副作用函数收集到 targetMap 变量中

targetMap 是个 WeakMap, WeakMap 和 Map 的区别在于 WeakMap 的键只能是对象, 用 WeakMap 而不用 Map 是因为 Proxy 对象不能代理普通数据类型

targetMap 的结构:

const targetMap = {
[target]: {
[key1]: [effect1, effect2, effect3, ...],
[key2]: [effect1, effect2, effect3, ...]
}
}

{ text: '' } 这个target 传进来时, targetMap 的结构是:

// 上边例子中用来在 id 为 Box 的 div 中输出当前时间的副作用函数
const effect = () => {
document.getElementById("Box").innerText = date.text;
}; const target = {
"{ text: '' }": {
"text": [effect]
}
}

举三个例子, 来分析一下 targetMap 的结构, 第一个例子是多个 target 情况:

<script>
import { effect, reactive } from "@vue/reactivity"; const target1 = { language: "JavaScript"};
const target2 = { language: "Go"};
const target3 = { language: "Python"};
const r1 = reactive(target1);
const r2 = reactive(target2);
const r3 = reactive(target3); // effect1
effect(() => {
console.log(r1.language);
}); // effect2
effect(() => {
console.log(r2.language);
}); // effect3
effect(() => {
console.log(r3.language);
}); // effect4
effect(() => {
console.log(r1.language);
console.log(r2.language);
console.log(r3.language);
});
</script>

这种情况下 targetMap 的构成是:

const effect1 = () => {
console.log(r1.language);
};
const effect2 = () => {
console.log(r2.language);
};
const effect3 = () => {
console.log(r3.language);
};
const effect4 = () => {
console.log(r1.language);
console.log(r2.language);
console.log(r3.language);
}; const targetMap = {
'{"language":"JavaScript"}': {
"language": [effect1, effect4]
},
'{"language":"Go"}': {
"language": [effect2, effect4]
},
'{"language":"Python"}': {
"language": [effect3, effect4]
}
}

第二个例子是单个 target 多个属性时:

import { effect, reactive } from "@vue/reactivity";
const target = { name: "rmlzy", age: "27", email: "rmlzy@outlook.com"};
const user = reactive(target); effect(() => {
console.log(user.name);
console.log(user.age);
console.log(user.email);
});

这种情况下 targetMap 的构成是:

const effect = () => {
console.log(user.name);
console.log(user.age);
console.log(user.email);
}; const targetMap = {
'{"name":"rmlzy","age":"27","email":"rmlzy@outlook.com"}': {
"name": [effect],
"age": [effect],
"email": [effect]
}
}

第三个例子是多维对象时:

import { effect, reactive } from "@vue/reactivity";
const target = {
name: "rmlzy",
skills: {
frontend: ["JS", "TS"],
backend: ["Node", "Python", "Go"]
}
};
const user = reactive(target); // effect1
effect(() => {
console.log(user.name);
}); // effect2
effect(() => {
console.log(user.skills);
}); // effect3
effect(() => {
console.log(user.skills.frontend);
}); // effect4
effect(() => {
console.log(user.skills.frontend[0]);
});

这种情况下 targetMap 的构成是:

const effect1 = () => {
console.log(user.name);
};
const effect2 = () => {
console.log(user.skills);
};
const effect3 = () => {
console.log(user.skills.frontend);
};
const effect4 = () => {
console.log(user.skills.frontend[0]);
}; const targetMap = {
'{"name":"rmlzy","skills":{"frontend":["JS","TS"],"backend":["Node","Python","Go"]}}': {
"name": [effect1],
"skills": [effect2, effect3, effect4]
},
'{"frontend":["JS","TS"],"backend":["Node","Python","Go"]}': {
"frontend": [effect3, effect4]
}
}

第 4 步

第 3 步的目的是收集依赖, 这一步的目的是消费依赖

这里要注意, 只有当 target 代理对象的 get 方法被触发时, 才会真正执行 track, 换句话说, 没有地方需要 get target 对象时, target 没有依赖, 也就没有收集依赖一说

下边的例子中只是把 target 转换为了响应式对象, 并没有触发依赖收集, targetMap 是空的

const target = {"text": ""};
const date = reactive(target);
effect(() => {
date.text = new Date().toString();
});

第 2 步介绍了 createSetter 方法的核心是调用 trigger 方法, trigger 方法由 @/vue/reativity/src/effect.ts 提供, 下面看一下 trigger 的实现:

export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
const effects = new Set<ReactiveEffect>()
if (isMap(target)) {
effects.add(depsMap.get(ITERATE_KEY))
}
const run = (effect: ReactiveEffect) => {
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}

trigger 的实现很简单, 先把 target 相关的 effect 汇总到 effects 数组中, 然后调用 effects.forEach(run) 执行所有的副作用函数

再回顾一下 effect 方法的定义: effect(fn, options), 其中 options 有个可选属性叫 scheduler, 从上边 run 函数也可以看到 scheduler 的作用是让用户自定义如何执行副作用函数

第 5 步

又回到了本文最开始讲的 effect, effect 函数的实现如下:

export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}

effect 的核心是调用 createReactiveEffect 方法

可以看到 options.lazy 默认为 false 会直接执行 effect, 当设置为 true 时, 会返回 effect 由用户手动触发

createReactiveEffect 函数的实现如下:

const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}

首先定义了 effect 是个普通的 function, 先看后边 effect 函数挂载的属性:

effect.id = uid++ // 自增ID, 每个 effect 唯一的ID
effect.allowRecurse = !!options.allowRecurse // 是否允许递归
effect._isEffect = true // 特殊标记
effect.active = true // 激活状态
effect.deps = [] // 依赖数组
effect.raw = fn // 缓存一份用户传入的副作用函数
effect.options = options // 缓存一份用户传入的配置

isEffect 函数用来判断值是否是 effect, 就是根据上边 _isEffect 变量判断的, isEffect 函数实现如下:

function isEffect(fn) {
return fn && fn._isEffect === true;
}

再来看 effect 的核心逻辑:

cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}

effectStack 用数组实现栈, activeEffect 是当前生效的 effect

先执行 cleanup(effect):

function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}

cleanup 的目的是清空 effect.deps, deps 是持有该 effect 的依赖数组, deps 的结构如下

清除完依赖后, 开始重新收集依赖, 把当前 effect 追加到 effectStack, 将 activeEffect 设置为当前的 effect, 然后调用 fn 并且返回 fn() 的结果

第 4 步提过到: "只有当 target 代理对象的 get 方法被触发时, 才会真正执行 track", 至此才是真正的触发了 target代理对象的 get 方法, 执行了track 方法然后收集到了依赖

等到 fn 执行结束, finally 阶段, 把当前的 effect 弹出, 恢复 effectStack 和 activeEffect, Vue3 整个响应式的流程到此结束

三、知识点

activeEffect 的作用

我的理解是为了暴露给 onTrack 方法, 来整体看一下 activeEffect 出现的地方:

let activeEffect;

function effect(fn, options = EMPTY_OBJ) {
const effect = createReactiveEffect(fn, options);
return effect;
} function createReactiveEffect(fn, options) {
const effect = function reactiveEffect() {
// 省略部分代码 ...
try {
activeEffect = effect;
return fn();
}
finally {
activeEffect = effectStack[effectStack.length - 1];
}
};
// 省略部分代码 ...
return effect;
} function track(target, type, key) {
if (activeEffect === undefined) {
return;
}
let dep = targetMap.get(target).get(key); // dep 是存储 effect 的 Set 数组
if (!dep.has(activeEffect)) {
dep.add(activeEffect);
activeEffect.deps.push(dep);
if (activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
});
}
}
}
  1. fn 执行前, activeEffect 被赋值为当前 effect

  2. fn 执行时的依赖收集阶段, 获取 targetMap 中的 dep (存储 effect 的 Set 数组), 并暴露给 options.onTrack 接口

effect 和 stop

@vue/reactivity 提供了 stop 函数, effect 可以被 stop 函数终止

const obj = reactive({ foo: 0 });

const runner = effect(() => {
console.log(obj.foo);
}); // effect 被执行一次, 输出 0 // obj.foo 被赋值一次, effect 被执行一次, 输出 1
obj.foo ++; // 停止 effect
stop(runner); // effect 不会被触发, 无输出
obj.foo ++;

watchEffect 和 effect

  1. watchEffect 来自 @vue/runtime-core, effect 来自 @vue/reactivity
  2. watchEffect 基于 effect 实现
  3. watchEffect 会维护与组件实例的关系, 如果组件被卸载, watchEffect 会被 stop, 而 effect 不会被 stop

watchEffect 和 invalidate

watchEffect 接收的副作用函数, 会携带一个 onInvalidate 的回调函数作为参数, 这个回调函数会在副作用无效时执行

watchEffect(async (onInvalidate) => {
let valid = true;
onInvalidate(() => {
valid = false;
});
const data = await fetch(obj.foo);
if (valid) {
// 获取到 data
} else {
// 丢弃
}
});

ref

JS数据类型:

  • 基本类型: String、Number、Boolean、Null、Undefined、Symbol
  • 引用数据类型: Object、Array、Function

因为 Proxy 只能代理对象, reactive 函数的核心又是 Proxy, 所以 reactive 不能代理基本类型

对于基本类型需要用 ref 函数将基本类型转为对象:

class RefImpl<T> {
private _value: T public readonly __v_isRef = true constructor(private _rawValue: T, public readonly _shallow = false) {
this._value = _shallow ? _rawValue : convert(_rawValue)
} get value() {
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
} set value(newVal) {
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}

其中 __v_isRef 参数用来标志当前值是 ref 类型, isRef 的实现如下:

export function isRef(r: any): r is Ref {
return Boolean(r && r.__v_isRef === true)
}

这样做有个缺点, 需要多取一层 .value:

const myRef = ref(0);
effect(() => {
console.log(myRef.value);
});
myRef.value = 1;

这也是 Vue ref 语法糖提案的原因, 可以参考 如何评价 Vue 的 ref 语法糖提案?

reactive 和 shallowReactive

shallowReactive 用来定义浅响应数据, 深层次的对象值是非响应式的:

const target = {
foo: {
bar: 1
}
};
const obj = shallowReactive(target); effect(() => {
console.log(obj.foo.bar);
}); obj.foo.bar = 2; // 无效, reactive 则有效
obj.foo = { bar: 2 }; // 有效

readonly 和 shallowReadonly

类似 shallowReactive, 深层次的对象值是可以被修改的

markRaw 和 toRaw

markRaw 的作用是让数据不可被代理, 所有携带 __v_skip 属性, 并且值为 true 的数据都会被跳过:

export function markRaw<T extends object>(value: T): T {
def(value, ReactiveFlags.SKIP, true)
return value
}

toRaw 的作用是获取代理对象的原始对象:

const obj = {};
const reactiveProxy = reactive(obj);
console.log(toRaw(reactiveProxy) === obj); // true

computed

const myRef = ref(0);
const myRefComputed = computed(() => {
return myRef.value * 2;
});
effect(() => {
console.log(myRef.value * 2);
});

myRef 值变化时, computed 会执行一次, effect 会执行一次

myRef 值未变化时, computed 不会执行, effect 依旧会执行


如果你有问题欢迎留言和我交流, 阅读原文

Vue3 源码之 reactivity的更多相关文章

  1. Typescript | Vue3源码系列

    TypeScript 是开源的,TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript.编译出来的 JavaScript 可以运行在任何浏览器上.TypeS ...

  2. vue3源码难学,先从petite-vue开始吧

    如今这个世道,作为一个有几年工作经验的前端,不学点框架源码都感觉要被抛弃了,react或vue要能吹吹牛吧,最好能造个轮子,听说vue3源码好学点,那么学学vue3,但是学起来还是那么费劲,感觉快放弃 ...

  3. vue3源码node的问题

    下载vue3源码后,下载依赖时,node的版本需要在10.0.0以上,并且不同的vue3里面的插件的配置对版本依赖还不同,14.0.0以上的版本基本都不支持win7了, win7系统可以安装12.0. ...

  4. Vue3源码分析之 Ref 与 ReactiveEffect

    Vue3中的响应式实现原理 完整 js版本简易源码 在最底部 ref 与 reactive 是Vue3中的两个定义响应式对象的API,其中reactive是通过 Proxy 来实现的,它返回对象的响应 ...

  5. Vue3源码分析之Diff算法

    Diff 算法源码(结合源码写的简易版本) 备注:文章后面有详细解析,先简单浏览一遍整体代码,更容易阅读 // Vue3 中的 diff 算法 // 模拟节点 const { oldVirtualDo ...

  6. Vue3源码解析(computed-计算属性)

    作者:秦志英 前言 上一篇文章中我们分析了Vue3响应式的整个流程,本篇文章我们将分析Vue3中的computed计算属性是如何实现的. 在Vue2中我们已经对计算属性了解的很清楚了,在Vue3中提供 ...

  7. Vue3源码分析之微任务队列

    参考资料:https://zh.javascript.info/microtask-queue#wei-ren-wu-dui-lie-microtaskqueue 简化版 Vue3 中的 微任务队列实 ...

  8. Vue3全局APi解析-源码学习

    本文章共5314字,预计阅读时间5-15分钟. 前言 不知不觉Vue-next的版本已经来到了3.1.2,最近对照着源码学习Vue3的全局Api,边学习边整理了下来,希望可以和大家一起进步. 我们以官 ...

  9. Vue3中的响应式对象Reactive源码分析

    Vue3中的响应式对象Reactive源码分析 ReactiveEffect.js 中的 trackEffects函数 及 ReactiveEffect类 在Ref随笔中已经介绍,在本文中不做赘述 本 ...

随机推荐

  1. Core3.0读取appsetting.json中的配置参数

    前言 方法很多,下面的例子也是从百度上搜索到的,原文链接已经找不到了. 方法1 1.添加NovelSetting节点,写入相关的配置信息 2.创建类,字段与上面的配置一致 3.StartUp.cs中获 ...

  2. 微信小程序-页面下拉

    微信小程序当滑动到最顶部和最底部时,继续下拉,会将整个页面拉下去或者拉上去,本来以为是客户端自有的特性,就没去管他,直到我的禅道出现了这个记录... 其实这个问题是可以解决的,只需要在你不想出现在此情 ...

  3. 单细胞分析实录(3): Cell Hashing数据拆分

    在之前的文章里,我主要讲了如下两个内容:(1) 认识Cell Hashing:(2): 使用Cell Ranger得到表达矩阵.相信大家已经知道了cell hashing与普通10X转录组的差异,以及 ...

  4. lua脚本简介

    Lua [1]  是一个小巧的脚本语言.它是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由Roberto Ier ...

  5. 从 Eclipse 到 IDEA,金字塔到太空堡垒【转]

    https://blog.csdn.net/X5fnncxzq4/article/details/83829223 工欲善其事,必先利其器.对于程序员来说,具有生产力的工具能让你事半功倍,心情大好.两 ...

  6. EF Core CodeFirst数据库自动迁移

    开发过程中都会遇到数据库数据结构更新的问题,怎么对数据库更新进行版本控制呢? 不同的项目对数据库版本更新控制的方式不同,常用的有第三方Evolve,开发人员将数据库更新脚本按照版本号的放在一起,然后执 ...

  7. python 中的sum( )函数 与 numpy中的 sum( )的区别

    一. python sum函数 描述: sum() 对序列进行求和 用法: sum(iterable[, start]) iterable:可迭代对象,例如,列表,元组,集合. start:指定相加的 ...

  8. Dubbo 就是靠它崭露头角!(身为开源框架很重要的一点)

    Hola,我是 yes. 经过了 RPC 核心和 Dubbo 微内核两篇文章后,今天终于要稍稍深入一波 Dubbo 了. 作为一个通用的 RPC 框架,性能是很重要的一环,而易用性和扩展性也极为重要. ...

  9. vue 侦听器watch 之 深度监听 deep

    <template> <div> <p>FullName: {{person.fullname}}</p> <p>FirstName: &l ...

  10. 基于 MPI 的快速排序算法的实现

    完整代码: #include <iostream> #include <cstdlib> #include <ctime> #include <algorit ...