资讯专栏INFORMATION COLUMN

FE.SRC-Vue实战与原理笔记

wangjuntytl / 723人阅读

摘要:超出此时间则渲染错误组件。元素节点总共有种类型,为表示是普通元素,为表示是表达式,为表示是纯文本。

实战 - 插件 form-validate


  • {{ error }}
i18n


{{ $t("welcome-message") }}

实战 - 组件

Vue组件=Vue实例=new Vue(options)

属性

自定义属性 props:组件props中声明的属性

原生属性 attrs:没有声明的属性,默认自动挂载到组件根元素上

特殊属性 class,style:挂载到组件根元素上

事件

普通事件 @click,@input,@change,@xxx 通过this.$emit("xxx")触发

修饰符事件 @input.trim @click.stop

插槽

v-slot:xxx

v-slot:xxx="props"

相同名称的插槽替换

动态导入/延迟加载组件

基于路由拆分

const routes = [
  { path: /foo", component: () => import("./RouteComponent.vue") }
]
函数式组件

无状态 、实例、this上下文、生命周期

临时变量组件

  


批量渲染标签组件

封装应用实例化函数
function createApp ({ el, model, view, actions }) {
  const wrappedActions={}
  Object.keys(actions).forEach(key=>{
    const originalAction=actions[key]
    wrappedActions[key]=()=>{
      const nextModel=originalAction(model)
      vm.model=nextModel
    }
  })

  const vm=new Vue({
    el,
    data:{model},
    render(h){
      return view(h,this.model,wrappedActions)
    }
  })
}

createApp({
  el: "#app",
  model: {
    count: 0
  },
  actions: {
    inc: ({ count }) => ({ count: count + 1 }),
    dec: ({ count }) => ({ count: count - 1 })
  },
  view: (h, model, actions) => h("div", { attrs: { id: "app" }}, [
    model.count, " ",
    h("button", { on: { click: actions.inc }}, "+"),
    h("button", { on: { click: actions.dec }}, "-")
  ])
})
高阶组件

这是一个具名插槽
异步组件
const AsyncComp = () => ({
  // 需要加载的组件。应当是一个 Promise
  component: import("./MyComp.vue"),
  // 加载中应当渲染的组件
  loading: LoadingComp,
  // 出错时渲染的组件
  error: ErrorComp,
  // 渲染加载中组件前的等待时间。默认:200ms。
  delay: 200,
  // 最长等待时间。超出此时间则渲染错误组件。默认:Infinity
  timeout: 3000
})
Vue.component("async-example", AsyncComp)
实战 - 组件通信 父传子props







子传父组件$emit, $on, $off

在组件中,可以使用 $emit, $on, $off 分别来分发、监听、取消监听事件

// NewTodoInput ---------------------
// ...
methods: {
  addTodo: function () {
    eventHub.$emit("add-todo", { text: this.newTodoText })
    this.newTodoText = ""
  }
}
// DeleteTodoButton ---------------------
// ...
methods: {
  deleteTodo: function (id) {
    eventHub.$emit("delete-todo", id)
  }
}
// Todos ---------------------
// ...
created: function () {
  eventHub.$on("add-todo", this.addTodo)
  eventHub.$on("delete-todo", this.deleteTodo)
},
// 最好在组件销毁前
// 清除事件监听
beforeDestroy: function () {
  eventHub.$off("add-todo", this.addTodo)
  eventHub.$off("delete-todo", this.deleteTodo)
},
methods: {
  addTodo: function (newTodo) {
    this.todos.push(newTodo)
  },
  deleteTodo: function (todoId) {
    this.todos = this.todos.filter(function (todo) {
      return todo.id !== todoId
    })
  }
}
内置$parent、$children、$ref;$attrs和$listeners

$ref ref="xxx"
$parent / $children:访问父 / 子实例


 
高阶插件/组件库 provide & inject(observable)
// 父级组件提供 "foo"
var Provider = {
  provide: {
    foo: "bar"
  },
  // ...
}

// 子组件注入 "foo"
var Child = {
  inject: ["foo"],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}
// 父级组件提供 "state"
var Provider = {
  provide: {
    state = Vue.observable({ count: 0 })
  },
  // ...
}

// 子组件注入 "foo"
var Child = {
  inject: ["state"],
  created () {
    console.log(this.state) // => { count: 0 }
  }
  // ...
}
全局对象 Event Bus
const state={count:0}

const Counter = {
  data(){return state},
  render:h=>h("div",state.count)
}

new Vue({
  el: "#app",
  components:{Counter},
  methods:{
    inc(){
      state.count++
    }
  }
})
//中央事件总线
var bus = new Vue();
var app = new Vue({
  el: "#app",
  template: `
            
` }); // 在组件 brother1 的 methods 方法中触发事件 bus.$emit("say-hello", "world"); // 在组件 brother2 的 created 钩子函数中监听事件 bus.$on("say-hello", function(arg) { console.log("hello " + arg); // hello world });
自实现boradcast和dispatch

**$dispatch 和 $broadcast 已经被弃用,使用
Vuex代替**

以下自实现参考 iview/emitter.js at 2.0 · iview/iview -github

function broadcast(componentName, eventName, params) {
    this.$children.forEach(child => {
        const name = child.$options.name;

        if (name === componentName) {
            child.$emit.apply(child, [eventName].concat(params));
        } else {
            // todo 如果 params 是空数组,接收到的会是 undefined
            broadcast.apply(child, [componentName, eventName].concat([params]));
        }
    });
}
export default {
    methods: {
        dispatch(componentName, eventName, params) {
            let parent = this.$parent || this.$root;
            let name = parent.$options.name;

            while (parent && (!name || name !== componentName)) {
                parent = parent.$parent;

                if (parent) {
                    name = parent.$options.name;
                }
            }
            if (parent) {
                parent.$emit.apply(parent, [eventName].concat(params));
            }
        },
        broadcast(componentName, eventName, params) {
            broadcast.call(this, componentName, eventName, params);
        }
    }
};
原理 - 响应式

单向数据流,双向绑定语法糖

demo


原理

数据劫持+观察订阅模式:

在读取属性的时候依赖收集,在改变属性值的时候触发依赖更新

实现一个observer,劫持对象属性

实现一个全局的订阅器Dep,可以追加订阅者,和通知依赖更新

在读取属性的时候追加当前依赖到Dep中,在设置属性的时候循环触发依赖的更新

new Vue(options)创建实例的时候,initData()进行数据劫持

通过Object.defineProperty(obj,key,desc)对data进行数据劫持,即创建get/set函数

这里需要考虑对对象的以及对数组的数据劫持(支持 push,pop,shift,unshift,splice,sort,reverse,不支持 filter,concat,slice)

对象递归调用

数组变异方法的解决办法:代理原型/实例方法

避免依赖重读Observer

  // 全局的依赖收集器Dep
   window.Dep = class Dep {
       constructor() {this.subscribers = new Set()}
       depend() {activeUpdate&&this.subscribers.add(activeUpdate)}
       notify() {this.subscribers.forEach(sub =>sub())}
   }
   let activeUpdate
   function autorun(update) {
       function wrapperUpdate() {
           activeUpdate = wrapperUpdate
           update()
           activeUpdate = null
       }
       wrapperUpdate()
   }
   function observer(obj) {
       Object.keys(obj).forEach(key => {
           var dep = new Dep()
           let internalValue = obj[key]
           Object.defineProperty(obj, key, {
               get() {
                   // console.log(`getting key "${key}": ${internalValue}`)
                   // 将当前正在运行的更新函数追加进订阅者列表
                   activeUpdate&&dep.depend() //收集依赖
                   return internalValue
               },
               set(newVal) {
                //console.log(`setting key "${key}" to: ${internalValue}`)
                // 加个if判断,数据发生变化再触发更新
                if(internalValue !== newVal) {
                       internalValue = newVal
                       dep.notify() // 触发依赖的更新
                }
               }
           })
       })
   }
   let state = {count:0}
   observer(state);
   autorun(() => {
       console.log("state.count发生变化了", state.count)
   })
   state.count = state.count + 5;
   // state.count发生变化了 0
   // state.count发生变化了 5
MVVM实现

实现mvvm-github

Model-View-ViewModel,其核心是提供对View 和 ViewModel 的双向数据绑定,这使得ViewModel 的状态改变可以自动传递给 View

observer

proxy 方法遍历 data 的 key,把 data 上的属性代理到 vm 实例上(通过Object.defineProperty 的 getter 和 setter )

observe(data, this) 给 data 对象添加 Observer做监听。

创建一个 Observer 对象

创建了一个 Dep 对象实例(观察者模式)

遍历data,convert(defineReactive) 方法使他们有getter、setter

getter 和 setter 方法调用时会分别调用 dep.depend 方法和 dep.notify

depend:把当前 Dep 的实例添加到当前正在计算的Watcher 的依赖中

notify:遍历了所有的订阅 Watcher,调用它们的 update 方法

computed

computed初始化被遍历computed,使用Object.defineProperty进行处理,依赖收集到Dep.target

触发data值时会触发Watcher监听函数

computed值缓存 watcher.dirty决定了计算属性值是否需要重新计算,默认值为true,即第一次时会调用一次。每次执行之后watcher.dirty会设置为false,只有依赖的data值改变时才会触发

mixin

全局注册的选项,被引用到你的每个组件中
1、Vue.component 注册的 【全局组件】
2、Vue.filter 注册的 【全局过滤器】
3、Vue.directive 注册的 【全局指令】
4、Vue.mixin 注册的 【全局mixin】

合并权重
1、组件选项
2、组件 - mixin
3、组件 - mixin - mixin
4、.....
x、全局 选项
函数合并叠加(data,provide)
数组叠加(created,watch)
原型叠加(components,filters,directives)
两个对象合并的时候,不会相互覆盖,而是 权重小的 被放到 权重大 的 的原型上
覆盖叠加(props,methods,computed,inject)
两个对象合并,如果有重复key,权重大的覆盖权重小的
直接替换(el,template,propData 等)

filter

something | myFilter 被解析成_f("myFilter")( something )

nextTick

Vue.js 在默认情况下,每次触发某个数据的 setter 方法后,对应的 Watcher 对象其实会被 push 进一个队列 queue 中,在下一个 tick 的时候将这个队列 queue 全部拿出来 run( Watcher 对象的一个方法,用来触发 patch 操作) 一遍。

原理 - virtaul DOM

真实DOM操作昂贵,虚拟DOM就是js对象,操作代价小

模板编译&渲染

平时开发写vue文件都是用模板template的方法写html,模板会被编译成render函数,流程如下:

初始化的时候

- 模板会被编译成render函数
- render函数返回虚拟DOM
- 生成真正的DOM

数据更新的时候

- render函数返回新的virtual Dom
- 新的virtual Dom和旧的virtual Dom做diff
- 将差异运用到真实DOM

render API

    //template, jsx, render本质都是一样的, 都是一种dom和数据状态之间关系的表示
    render(h) {
        h(tag, data, children)
    }
    // tag可以是原生的html标签
    render(h) {
        return h("div", { attrs: {}}, [])
    }
    // 也可以是一个vue component
    import vueComponent from "..."
    render(h) {
        h(vueComponent, {
            props: {} 
        })
    }

偏逻辑用render,偏视图用template

compile

compile 编译可以分成 parse、 optimize 与 generate 三个阶段,最终需要得到 render function。

parse:会用正则等方式解析 template 模板中的指令、class、style 等数据,形成 AST。

transclude(el, option) 把 template 编译成一段 document fragment

compileNode(el, options) 深度遍历DOM,正则解析指令

vm.bindDir(descriptor, node, host, scope) 根据 descriptor 实例化不同的 Directive 对象

Directive 在初始化时通过 extend(this, def) 扩展 bind 和 update,创建了 Watcher关联update

解析模板字符串生成 AST `const ast = parse(template.trim(), options)
循环解析 template,利用正则表达式顺序解析模板,当解析到开始标签、闭合标签、文本的时候都会分别执行对应的回调函数,来达到构造 AST 树的目的。 AST 元素节点总共有 3 种类型,type 为 1 表示是普通元素,为 2 表示是表达式,为 3 表示是纯文本。

optimize:标记 static 静态节点,而减少了比较的过程 等优化

优化语法树 optimize(ast, options)
深度遍历这个 AST 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变(标记静态节点 markStatic(root);标记静态根 markStaticRoots(root, false))

generate:是将 AST 转化成 render function 字符串

AST转可执行的代码 const code = generate(ast, options)

vue模板编译前后:

`
  • {{item}}:{{index}}
` with(this){ return (isShow) ? _c("ul", { staticClass: "list", class: bindCls }, _l((data), function(item, index) { return _c("li", { on: { "click": function($event) { clickItem(index) } } }, [_v(_s(item) + ":" + _s(index))]) }) ) : _e() }

对比react的jsx编译前后

`
Hello World
` h("div",{ id: "1", "class": "li-1" },"Hello World", h(MyComp, null) )

diff

vnode

{
  el:  div  //对真实的节点的引用,本例中就是document.querySelector("#id.classA")
  tagName: "DIV",   //节点的标签
  sel: "div#v.classA"  //节点的选择器
  data: null,       // 一个存储节点属性的对象,对应节点的el[prop]属性,例如onclick , style
  children: [], //存储子节点的数组,每个子节点也是vnode结构
  text: null,    //如果是文本节点,对应文本节点的textContent,否则为null
}

核心 patch (oldVnode, vnode)

key和sel相同才去比较,否则新替旧

patchVnode (oldVnode, vnode)节点比较5种情况

if (oldVnode === vnode),他们的引用一致,可以认为没有变化

if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text),文本节点的比较,需要修改,则会调用Node.textContent = vnode.text

if( oldCh && ch && oldCh !== ch ), 两个节点都有子节点,而且它们不一样,这样我们会调用updateChildren函数比较子节点

else if (ch),只有新的节点有子节点,调用createEle(vnode),vnode.el已经引用了老的dom节点,createEle函数会在老dom节点上添加子节点

else if (oldCh),新节点没有子节点,老节点有子节点,直接删除老节点

同层比较作用:将一棵树转换成另一棵树的最小操作次数是O(n^3),同层是O(1)

key的作用:

为了在数据变化时强制更新组件,以避免“原地复用”带来的副作用。

在交叉对比没有结果(列表数据的重新排序,插,删)的时候会采用key来提高这个diff速度(不设key,newCh和oldCh只会进行头尾两端的相互比较,设key后,除了头尾两端的比较外,还会从用key生成的对象oldKeyToIdx中查找匹配的节点,从而移动dom而不是销毁再创建)

vue&react vdom区别
Vue 很“ 嚣张 ”,它宣称可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,由于vue会跟踪每一个组件的依赖收集,通过setter / getter 以及一些函数的劫持,能够精确地知道变化,并在编译过程标记了static静态节点,在接下来新的Virtual DOM 并且和原来旧的 Virtual DOM进行比较时候,跳过static静态节点。所以不需要重新渲染整个组件树。

React默认是通过比较引用的方式进行,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。如果想避免不必要的子组件重新渲染,你需要在所有可能的地方使用PureComponent,或者手动实现shouldComponentUpdate方法。但是Vue中,你可以认定它是默认的优化。

摘自 https://juejin.im/post/5b6178...

vdom实现

类vue vdom

snabbdom-github

snabbdom源码阅读分析

Vue 2.0 的 virtual-dom 实现简析

类react vdom

preact-github

preact工作原理

原理 - Router(路由)

vue插件,通过hash /history 2中方式实现可配路由
Hash

push(): 设置新的路由添加历史记录并更新视图,常用情况是直接点击切换视图,调用流程:

$router.push() 显式调用方法

HashHistory.push() 根据hash模式调用,设置hash并添加到浏览器历史记录(window.location.hash= XXX)

History.transitionTo() 开始更新

History.updateRoute() 更新路由

app._route= route

vm.render() 更新视图

replace: 替换当前路由并更新视图,常用情况是地址栏直接输入新地址,流程与push基本一致

但流程2变为替换当前hash (window.location.replace= XXX)
3.监听地址栏变化:在setupListeners中监听hash变化(window.onhashchange)并调用replace

History

push:与hash模式类似,只是将window.hash改为history.pushState

replace:与hash模式类似,只是将window.replace改为history.replaceState

监听地址变化:在HTML5History的构造函数中监听popState(window.onpopstate)

实战

const Foo = {
    props: ["id"],
    template: `
foo with id: {{id}}
` } const Bar = { template: `
bar
` } const NotFound = { template: `
not found
` } const routeTable = { "/foo/:id": Foo, "/bar": Bar, } const compiledRoutes = []; Object.keys(routeTable).forEach(path => { const dynamicSegments = [] const regex = pathToRegexp(path, dynamicSegments) const component = routeTable[path] compiledRoutes.push({ component, regex, dynamicSegments }) }) window.addEventListener("hashchange", () => { app.url = window.location.hash.slice(1); }) const app = new Vue({ el: "#app", data() { return { url: window.location.hash.slice(1) } }, render(h) { const url = "/" + this.url let componentToRender let props = {} compiledRoutes.some(route => { const match = route.regex.exec(url) if (match) { componentToRender = route.component route.dynamicSegments.forEach((segment,index) => { props[segment.name] = match[index+1] }) } }) return h("div", [ h("a", { attrs: { href: "#foo/123" } }, "foo123"), "|", h("a", { attrs: { href: "#foo/234" } }, "foo234"), "|", h("a", { attrs: { href: "#bar" } }, "bar"), h(componentToRender || NotFound, { props }) ]) } })
原理 - props(属性)

父组件怎么传值给子组件的 props

父组件的模板 会被解析成一个 模板渲染函数,执行时会绑定 父组件为作用域

(function() {
    with(this){ 
        return _c("div",{staticClass:"a"},[
            _c("testb",{attrs:{"child-name":parentName}})
        ],1)
    }
})

所以渲染函数内部所有的变量,都会从父组件对象 上去获取

组件怎么读取 props

子组件拿到父组件赋值过后的 attr,筛选出 props,然后保存到实例的_props 中,并逐一复制到实例上,响应式的。

父组件数据变化,子组件props如何更新
父组件数据变化,触发set,从而通知依赖收集器的watcher重新渲染

原理 - Vuex

vuex 仅仅是作为 vue 的一个插件而存在,不像 Redux,MobX 等库可以应用于所有框架,vuex 只能使用在 vue 上,很大的程度是因为其高度依赖于 vue 的 computed 依赖检测系统以及其插件系统,

vuex 整体思想诞生于 flux,可其的实现方式完完全全的使用了 vue 自身的响应式设计,依赖监听、依赖收集都属于 vue 对对象 Property set get 方法的代理劫持。vuex 中的 store 本质就是没有 template 的隐藏着的 vue 组件;

state

this.$store.state.xxx 取值

提供一个响应式数据

vuex 就是一个仓库,仓库里放了很多对象。其中 state 就是数据源存放地,对应于一般 vue 对象里面的 data

state 里面存放的数据是响应式的,vue 组件从 store 读取数据,若是 store 中的数据发生改变,依赖这相数据的组件也会发生更新

它通过 mapState 把全局的 state 和 getters 映射到当前组件的 computed 计算属性

getter

this.$store.getters.xxx 取值

借助vue计算属性computed实现缓存

getter 可以对 state 进行计算操作,它就是 store 的计算属性

虽然在组件内也可以做计算属性,但是 getters 可以在多给件之间复用

如果一个状态只在一个组件内使用,是可以不用 getters

mutaion

this.$store.commit("xxx") 赋值

更改state方法

action 类似于 muation, 不同在于:action 提交的是 mutation,而不是直接变更状态

action 可以包含任意异步操作

action

this.$store.dispatch("xxx") 赋值

触发mutation方法

module

Vue.set动态添加state到响应式数据中

简单版Vuex实现

//min-vuex
import Vue from "vue"
const Store = function Store (options = {}) {
  const {state = {}, mutations={}} = options
  this._vm = new Vue({
    data: {
      $$state: state
    },
  })
  this._mutations = mutations
}
Store.prototype.commit = function(type, payload){
  if(this._mutations[type]) {
    this._mutations[type](this.state, payload)
  }
}
Object.defineProperties(Store.prototype, { 
  state: { 
    get: function(){
      return this._vm._data.$$state
    }
  }
});
export default {Store}

使用 Vuex 只需执行 Vue.use(Vuex),并在 Vue 的配置中传入一个 store 对象的示例,store 是如何实现注入的?

Vue.use(Vuex) 方法执行的是 install 方法,它实现了 Vue 实例对象的 init 方法封装和注入,使传入的 store 对象被设置到 Vue 上下文环境的store中。因此在VueComponent任意地方都能够通过this.store 访问到该 store。

state 内部支持模块配置和模块嵌套,如何实现的?

在 store 构造方法中有 makeLocalContext 方法,所有 module 都会有一个 local context,根据配置时的 path 进行匹配。所以执行如 dispatch("submitOrder", payload)这类 action 时,默认的拿到都是 module 的 local state,如果要访问最外层或者是其他 module 的 state,只能从 rootState 按照 path 路径逐步进行访问。

在执行 dispatch 触发 action(commit 同理)的时候,只需传入(type, payload),action 执行函数中第一个参数 store 从哪里获取的?

store 初始化时,所有配置的 action 和 mutation 以及 getters 均被封装过。在执行如 dispatch("submitOrder", payload)的时候,actions 中 type 为 submitOrder 的所有处理方法都是被封装后的,其第一个参数为当前的 store 对象,所以能够获取到 { dispatch, commit, state, rootState } 等数据。

Vuex 如何区分 state 是外部直接修改,还是通过 mutation 方法修改的?

Vuex 中修改 state 的唯一渠道就是执行 commit("xx", payload) 方法,其底层通过执行 this._withCommit(fn) 设置_committing 标志变量为 true,然后才能修改 state,修改完毕还需要还原_committing 变量。外部修改虽然能够直接修改 state,但是并没有修改_committing 标志位,所以只要 watch 一下 state,state change 时判断是否_committing 值为 true,即可判断修改的合法性。

调试时的"时空穿梭"功能是如何实现的?

devtoolPlugin 中提供了此功能。因为 dev 模式下所有的 state change 都会被记录下来,"时空穿梭" 功能其实就是将当前的 state 替换为记录中某个时刻的 state 状态,利用 store.replaceState(targetState) 方法将执行 this._vm.state = state 实现。

原理 - SSR

Vue.js 是构建客户端应用程序的框架。默认情况下,可以在浏览器中输出 Vue 组件,进行生成 DOM 和操作 DOM。

然而,也可以将同一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。

服务器渲染的 Vue.js 应用程序也可以被认为是"同构"或"通用",因为应用程序的大部分代码都可以在服务器和客户端上运行。

服务端渲染的核心就在于:通过vue-server-renderer插件的renderToString()方法,将Vue实例转换为字符串插入到html文件

其他

vue 企业级应用模板-github

VueConf 2018 杭州(第二届Vue开发者大会)

Vue 3.0 进展 - 尤雨溪

参考资料

深入响应式原理 —— Vue.js官网

Advanced Vue.js Features from the Ground Up - 尤雨溪

Vue 源码解析:深入响应式原理 - 黄轶

Vue 源码研究会 - 神仙朱

vue组件之间8种组件通信方式总结 - zhoulu_hp

Vue问得最多的面试题 - yangcheng

剖析 Vue.js 内部运行机制 - 染陌

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109787.html

相关文章

  • 阿里 2021 版最全 Java 并发编程笔记,看完我才懂了“内卷”的真正意义

    摘要:纯分享直接上干货操作系统并发支持进程管理内存管理文件系统系统进程间通信网络通信阻塞队列数组有界队列链表无界队列优先级有限无界队列延时无界队列同步队列队列内存模型线程通信机制内存共享消息传递内存模型顺序一致性指令重排序原则内存语义线程 纯分享 , 直接上干货! 操作系统并发支持 进程管理内存管...

    不知名网友 评论0 收藏0
  • FE.SRC-React实战原理笔记

    摘要:异步实战状态管理与组件通信组件通信其他状态管理当需要改变应用的状态或有需要更新时,你需要触发一个把和载荷封装成一个。的行为是同步的。所有的状态变化必须通过通道。前端路由实现与源码分析设计思想应用是一个状态机,视图与状态是一一对应的。 React实战与原理笔记 概念与工具集 jsx语法糖;cli;state管理;jest单元测试; webpack-bundle-analyzer Sto...

    PumpkinDylan 评论0 收藏0
  • ApacheCN 编程/大数据/数据科学/人工智能学习资源 2019.5

    摘要:请回复这个帖子并注明组织个人信息来申请加入。版笔记等到中文字幕翻译完毕后再整理。数量超过个,在所有组织中排名前。网站日超过,排名的峰值为。主页归档社区自媒体平台微博知乎专栏公众号博客园简书合作侵权,请联系请抄送一份到赞助我们 Special Sponsors showImg(https://segmentfault.com/img/remote/1460000018907426?w=1...

    zhonghanwen 评论0 收藏0
  • ApacheCN 学习资源汇总 2019.3

    摘要:主页暂时下线社区暂时下线知识库自媒体平台微博知乎简书博客园合作侵权,请联系请抄送一份到特色项目中文文档和教程与机器学习实用指南人工智能机器学习数据科学比赛系列项目实战教程文档代码视频数据科学比赛收集平台,,剑指,经典算法实现系列课本课本描述 【主页】 apachecn.org 【Github】@ApacheCN 暂时下线: 社区 暂时下线: cwiki 知识库 自媒体平台 ...

    array_huang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<