Git Product home page Git Product logo

source-learn's Introduction

source-learn's People

Contributors

danarrr avatar

Watchers

 avatar

source-learn's Issues

Vue源码阅读:数据响应式

MVVM设计模型

image

包括三大要素:数据响应式模板引擎渲染

  1. 数据响应式:监听数据变化并在视图中更新
    • Object.defineProperty(vue v2)
    • Proxy(vue v3)
      通过拦截数据,收集数据依赖并通知更新,实现数据的双向绑定。
  2. 模版引擎:提供描述视图的模版语法
    • 插值:{{}}
    • 指令:v-bind,v-on,v-model,v-for,v-if
  3. 渲染:如何将模板转换为html
    • 模板 => vnode => dom
      数据响应式作为vue框架最大的亮点,接下来继续了解下响应式数据的设计模型。

观察者模式

什么是观察者模式?它分为注册环节发布环节

举例个场景:比如我去买蛋糕,蛋糕完成需要一段时间,所以蛋糕店会留下所有客户电话号码。等蛋糕完成,可以一次性通知所有记录了的客户。

场景中,
客户留下了电话号码 => 注册环节
蛋糕完成后通知客户 => 发布环节

实现简单的观察者模型类来描述这个流程

function Observer() {
  this.dep = []; 
  
  register(fn) {
    this.dep.push(fn)
  }
  
  notify() {
    this.dep.forEach(item => item())
  }
}

const wantCake = new Oberver();
// 每来一个顾客就注册一个想执行的函数
wantCake.register(() => {'console.log("call daisy")'})
wantCake.register(() => {'console.log("call anny")'})
wantCake.register(() => {'console.log("call sunny")'})

// 最后蛋糕做好之后,通知所有的客户
wantCake.notify()

Observer类被注册时,收集依赖数据(客户电话号码)到dep中,当蛋糕状态发生更新时(蛋糕做好了)通知notify()所有客户。

响应式原理

结合前面铺垫的知识和以下官方图,先记下三个重要的概念,助于理解源码。

  • Observer
    每个对象(包含子对象)有一个 Observer 实例,内部存在一个 Dep
  • Watcher
    每个组件都有一个Watcher实例。执行组件的初始化和更新方法。
  • Dep
    当对象初始化或者数据改变时,Dep负责通知Watcher进行更新。作为“管家角色”。

image
初始化时进行数据响应式操作创建组件 Watcher

组件Watcher在创建时会执行一次render()函数,从而触发相关数据相关keygetter方法,将该数据key收集到新创建的Dep中。当更改该数据时会触发依赖收集中相关keysetter方法,通过key的Dep通知Watcher进行更新

解析源码执行流程

从上一篇vue初始化流程的文章中,包含了初始化数据函数initState作为数据初始化与更新的切入点,开始观察代码的执行。

initState()

export function initState (vm: Component) {
  vm._watchers = []
  const opts = vm.$options
  // props
  if (opts.props) initProps(vm, opts.props)
  // methods
  if (opts.methods) initMethods(vm, opts.methods)
  // data响应式
  if (opts.data) {
    // 如果用户配置选项有data
    initData(vm)
  } else {
    observe(vm._data = {}, true /* asRootData */)
  }
  if (opts.computed) initComputed(vm, opts.computed)
  if (opts.watch && opts.watch !== nativeWatch) {
    initWatch(vm, opts.watch)
  }
}

除了对选项propsmethodsdatacomputedwatch优先级的处理,还处理了初始化响应式数据

function initData (vm: Component) {
  let data = vm.$options.data
  ...
  observe(data, true /* asRootData */)
}

observe(data, true)做了递归的响应式处理。 除了对象做响应式,子对象也需要数据响应式。

export function observe (value: any, asRootData: ?boolean): Observer | void {
  // 1. 获取__ob__ 实例
  let ob: Observer | void
  // 如果已经存在则直接使用
  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
    ob = value.__ob__
  } else if (...){
    // 不存在则初始化
    ob = new Observer(value)
  }
   if (asRootData && ob) {
    ob.vmCount++ // 计算递归的层级数
  }
  return ob
}

Observer实例

export class Observer {
  value: any;
  dep: Dep;
  vmCount: number; // number of vms that have this object as root $data

  constructor (value: any) {
    this.value = value
    // 一个对象创建一个dep实例,因为如果动态的增减属性,dep用来做变更通知dept.notify
    // Vue.set(obj, 'foo', 'foo')
    this.dep = new Dep()
    this.vmCount = 0
    // 创建dep实例
    def(value, '__ob__', this)
    if (Array.isArray(value)) {
      ...
      this.observeArray(value)
    } else {
      // 循环对象 做响应式
      this.walk(value)
    }
  }
  1. 判断是数组还是对象
  2. 创建一个dep实例,当对象如果动态增减属性 dep用来做变更通知this.walk(value)
  3. this.walk()通过遍历所有数据的key,对所有对象进行数据响应式,循环对象的每一个keys 定义defineReactive()

defineReactive()

export function defineReactive (
  obj: Object,
  key: string,
  val: any,
) {
  const dep = new Dep()

  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    get: function () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        // Vue2中一个组件一个watch 所以 dep n:1 Watch
        // 可是如果是用户手动创建的Watch那又有可能是 dep 1:n Watch 
        // 所以要解决成是 n:n   
        dep.depend()
        if (childOb) {
          // 子对象也要做依赖收集
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function (newVal) {
      const value = getter ? getter.call(obj) : val
      if (getter && !setter) return
      if (setter) {
        setter.call(obj, newVal)
      } else {
        val = newVal
      }
      childOb = !shallow && observe(newVal)
      dep.notify()
    }
  })
}
notify () {
    ...
    // sub收集的的Watch,遍历相关的Watch
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
}

总结下整个流程

第一步:组件初始化的时候,先给每一个Data属性都注册getter,setter,也就是reactive化。然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里,Data通过哈希表的方式收集对应的key到Dep实例中。

第二步:当data属性发生改变之后,会触发setter函数,通过Dep通知(会遍历sub里所有的watcher对象)对应的Watcher进行更新。进而修改视图层。

ps: 所有和视图相关数据的Vue都做了响应式处理,例如data/props..

横向对比v1 2 3

  • v1 Watcher实例的粒度为一个对象一个Watcher, 数据响应更快更及时所以也不需要vnode来维护,但是当数据庞大时消耗太多性能。

  • v2 使用的Object.definePropertyAPI
    缺点:

    • 对数组需要做特异性操作。给数组下标赋值触发不了数据更新,覆盖数组数据这部份原生方法'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' 。
    • 在一个对象的访问器属性中不能直接操作它的数据属性,也就是说无法给现有的数据属性设置访问器属性。
  • v3 使用Proxy API
    优点:

    • 可以对整个对象进行监听,省去了for...in循环提升效率
    • 省去额外的中间存储量
    • 可以监听数组,不用再单独对数组做特异性操作
      缺点:对浏览器旧版本兼容性低

Vue源码阅读:Vue的初始化流程

组件从初始化到挂载做了什么

初始化一个Vue实例

const app = new Vue({ // 断点
    el: '#demo',
    data:{foo:'foo'}
    // template: '<div>template</div>',
    // render(h){return h('div','render')},
}).$mount('#app’)

带着疑问来断点代码:
vue的初始化流程做了哪几件事?
vue实例是怎么挂载的?
常见的挂载方式除了$mount, 还有render/template,三者间有什么差别?

Vue的初始化

初始化全局API

Vue.set = set;
Vue.delete = del;
Vue.nextTick = nextTick;
initUse(Vue); // 实现Vue.use函数
initMixin(Vue); // 实现Vue.mixin函数
initExtend(Vue); // 实现Vue.extend函数
initAssetRegisters(Vue); // 注册实现Vue.component/directive/filter

注入了全局API,初始化Vue的静态方法,比如挂载全局组件Vue.component添加指令Vue.directive、使用插件Vue.use等。

定义Vue构造函数

function Vue (options) {
  ...
  this._init(options) // 进入下一个断点
}

initMixin(Vue) // 实现init函数
stateMixin(Vue)  // 状态相关的api  $data,$props,$set,$delete,$watch
eventsMixin(Vue) // 事件相关的api  $on,$once,$off,$emit...
lifecycleMixin(Vue) // 生命周期api _update,$forceUpdate,$destroy
renderMixin(Vue) // 渲染api _render,$nextTick

export default Vue

定义了Vue构造器,并且为Vue实例注入 状态、事件、渲染等相关API。

实例的初始化

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options?: Object) {
    if (options && options._isComponent) {
         vm.$options = mergeOptions(
         resolveConstructorOptions(vm.constructor),
         options || {},
         vm
      )
    }
     ...
     initLifecycle(vm) // $parent,$root,$children,$refs 
     initEvents(vm) // 处理父组件传递的事件和回调
     initRender(vm) // $slots,$scopedSlots,_c,$createElement 
     callHook(vm, 'beforeCreate') 
     initInjections(vm) // 获取注入数据 
     initState(vm) // 初始化props,methods,data,computed,watch 
     initProvide(vm) // 提供数据注入 callHook(vm, 'created')
   }
   ...
   if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

处理了三件事情:

  1. mergeOptions 合并当前vm实例下选项和用户配置的选项{ el: '#demo', data:{foo:'foo'} }
  2. 初始化Vue的数据、事件、属性new Vue位于生命周期最始端生命周期 , 伴随着生命周期的执行,为实例添加属性、添加事件监听(组件通信)、初始化渲染相关、执行数据响应式等等,所以在beforeCreate生命周期之前都是访问不到数据的(由以上代码可看出),并在created中才完成数据注入。
  3. 存储要渲染的宿主元素,将当前的el位置传入$mount,为后续挂载到$mount(el)内宿主元素做准备。

挂载过程

$mount

接下来,看下$mount内部的实现机制,Vue官网中包含了vue的完整版和runtime两个版本:

  • 完整版:同时包含编译器和运行时的版本。
  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。

完整版的核心源码
解答了三种渲染模板的优先级 render > template > el, 如果用户使用的不是render函数,则调用compileToFunctions()为$mount注入编译器,将template转换成render函数。

// 覆盖(扩展)$mount的方法, 为了解析模板
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element, // 宿主
  hydrating?: boolean
): Component {
  if (!options.render) {
     if(template){
        // 如果不存在 render 函数,则会将模板转换成render函数
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
      ...
       }
     else if(el) {
      }
  }
  return mount.call(this, el, hydrating)
}

runtime版本核心源码

Vue.prototype.__patch__ = inBrowser ? patch : noop;

Vue.prototype.$mount = function (el?: string | Element, hydrating?: boolean): Component {
  el = el && inBrowser ? query(el) : undefined;
  return mountComponent(this, el, hydrating);
};
  • 这里有个很重要的参数hydrating, 默认为false标识为Web端使用。
  • 之所以抽离$mount函数是为了支持多平台(例如Vue2中weex,虽然已经凉了),可以保证在vnode节点下多种不同的渲染方式。

继续观察下这个重要的函数mountComponent()

mountComponent

里程碑函数: vnode => dom
$mount()API 初始化时调用mountComponent() ,将vnode通过__patch()__转换为dom,并追加到$mount中的el元素。

mountComponent()
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
) { 
   let updateComponent
   ...
   updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
   ...
   new Watcher(vm, updateComponent, noop, {
      before () {
         if (vm._isMounted && !vm._isDestroyed) {
           callHook(vm, 'beforeUpdate')
         }
       }
    }, true /* isRenderWatcher */)
  hydrating = false

  if (vm.$vnode == null) {
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

代码的执行流程:

  • updateComponent() // 声明函数, 暂未执行
  • new Watch() // 负责响应式数据依赖收集,代码执行new Watch实例时调用了updateComponent()函数执行_update()函数
  • _update()
    • _render() 获取到vnode
    • patch() 判断之前是否旧的vnode,如果没有则负责初始化,否则进行更新。vm.$el = vm.__patch__(xxx)在将vnode追加到真实dom中。断点执行到这一步时真实dom已渲染
      • dom Diff[todo domdiff文章]
 Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode

    vm._vnode = vnode
    if (!prevVnode) {
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
   
    ...
  }

生命周期的应用

  • 分类列举:
    • 初始化阶段:beforeCreate、created、beforeMount、mounted
    • 更新阶段:beforeUpdate、updated
    • 销毁阶段:beforeDestroy、destroyed
  • 应用:
    • created时,所有数据准备就绪,适合做数据获取、赋值等数据操作
    • mounted时,$el已生成,可以获取dom;子组件也已挂载,可以访问它们
    • updated时,数值变化已作用于dom,可以获取dom最新状态
    • destroyed时,组件实例已销毁,适合取消定时器等操作

Redux实现原理,尝试实现React-Redux

知识储备

什么是reducer?

reducer 就是⼀个纯函数,接收旧的 state 和 action,返回新的state。
之所以命名为reducer是因为这种函数与被传入Array.prototype.reduce(reducer, ?initialValue) ⾥的回调函数属于相同的类型。

function f1(arg) {console.log("f1", arg);return arg; }
function f2(arg) {console.log("f2", arg);return arg; }
function f3(arg) {console.log("f3", arg);return arg; }

思考:有如上函数, 聚合成⼀个函数,并把第⼀个函数的返回值传递给下⼀个函数,如何处理。

function compose(...funcs) {
    return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
console.log(compose(f1, f2, f3)("omg"));

如上实现了一个聚合函数,等价于f1(f2(f3("omg"))), 免去了层层套娃的写法。

Redux

Redux是javascript应用的状态容器,结合图解理解同步函数数据流的流向。

image

Redux的使用可以理解为一个累加器:

  1. 新建一个数据仓库store来存储数据
  2. store⾥的reducer初始化state并定义state修改规则
  3. 通过dispatch⼀个action来提交对数据的修改
  4. action提交到reducer函数⾥,根据传⼊的action的type,返回新的state

Redux核心实现

export default function createStore(reducer) { 
  let currentState
  let currentListeners = []

  // get
  function getState() {
    return currentState
  }

  // set
  function dispatch(action) {
    currentState = reducer(currentState, action)

    // state改变,执行订阅函数
    currentListeners.forEach(listener => listener())
  }

  function subscribe(listener){
    currentListeners.push(listener)

    // 订阅时也执行一次
    return (() => {
      const index = currentListeners.indexOf(listener)
      currentListeners.splice(index, 1)
    })
  }

  // 初始化触发一次
  dispatch({type: "REDUX/XXXXXXXXXXXXXXXXXXXXXXXX"});

  return {
    getState,
    dispatch,
    subscribe,
  };
}

redux的源码也很简短,利用闭包管理了 state 等变量,然后在 dispatch 的时候通过用户定义 reducer 拿到新状态赋值给 state,再把外部通过 subscribe 的订阅给触发一下。

Redux 异步 MiddleWare

Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延迟,⽹络请求,需要中间件的⽀持。

中间件其实就是⼀个函数,对 store.dispatch ⽅法进⾏改造,在发出 Action 和执⾏ Reducer 这两步之
间,添加了其他功能。
image

在createStore上进行扩展(添加enhancer参数),扩展一个中间件加强dispatch的功能。
调用:

export default function createStore(reducer, enhancer) {
  // enhancer 加强函数 专门处理异步函数扩展reducer的功能
  if(enhancer){
    // 1.enhancer是个函数
    // 2.dispatch来源于createStore
    // 3.最终还是修改state, 所以存下他的规则
    return enhancer(createStore)(reducer)
    ...
  }
}
import thunk from "redux-thunk";
const store = createStore(
  countReducer,
  applyMiddleware(thunk)
);

如果有多个reducer呢?redux提供了combineReducers包解决来处理多个reducer。

import {combineReducers} from "redux";

const store = createStore(
  combineReducers({user: loginReducer}),
  applyMiddleware(thunk, sagaMiddleware)
);

Middleware核心源码

export default function applyMiddleware(...middlewares) {
  // middles为接受的thunk中间件 这里可能不止一个thunk

  return createStore => reducer => {
    const store = createStore(reducer)
    let dispatch = store.dispatch

    // 加强dispatch
    // 1. 给中间件权限 读取和修改的权限
    let midAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)  // 作用域之和上下文有关
    }
    // 完整的中间件的库(数组)
    const middleChain = middlewares.map(middleware => middleware(midAPI)) // 这里的middleware就是thunk
     // 让这些函数按顺序执行
     dispatch = compose(...middleChain)(store.dispatch) // compose让所有的中间件按顺序去执行,用dispatch来执行(是不是看下thunk的next 类似一个形参)

    return {
      // 最终返回结果: 加强dispatch
      ...store,
      dispatch,
    }
  }
}

除了middlearea, 还引入了redux-thunk,redux-thunk主要的功能就是可以让我们dispatch一个函数,而不只是普通的 Object。
thunk的实现也很灵活,它的核心代码其实只有两行,就是判断每个经过它的action:如果是function类型,就调用这个function(并传入 dispatch 和 getState 及 extraArgument 为参数),而不是任由让它到达 reducer,因为 reducer 是个纯函数,Redux 规定到达 reducer 的 action 必须是一个 object 类型。

React-Redux

有了redux为何还需要 react-redux

redux注册store数据仓库时总需要订阅和取消订阅对数据的消费,使用起来十分繁琐。

React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来,利用Context从祖代向下传递数据。

import { connect } from 'react-redux'
@connect(
  // mapStateToProps 把state map(映射) props上一份
  (state) => {
    return {count: state.count};
  },
 {
     // mapDispatchToProps object | function 映射到props上
     add: () => ({type: "ADD"}),
     minus: () => ({type: "MINUS"}),
  }
);

React-Redux核心源码,实现connect

  1. 将数据仓库放到最顶层, 用Context Api不用显示传递数据
  2. 处理mapStateToProps处理state 并映射到props上
  3. 处理mapDispatchToProps 处理dispatch(obj || func)映射到props上
const Context = React.createContext()
export function Provider ({store, children}){
  return (<Context.Provider value={store}>{children}</Context.Provider>)
}

// 开始消费
// connect 的调用方式 : connect(state => state)(ReactReduxPage);
export const connect = (mapStateToProps, mapDispatchToProps) => ( WrappedComponent) => (props) => {
  // props组件本身的props属性
  // stateprops映射表 state可以订阅store获取数据
  // dispatchprops映射表

  const store = useContext(Context)
  const { getState, dispatch } = store
  const stateProps = mapStateToProps(getState())
  let dispatchProps = {dispatch} // 订阅state的变更

  // const forceUpdate = useForceUpdate();
  const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
  useLayoutEffect(() => {
    const unSubscribe = store.subscribe(() =>{
      forceUpdate()
    })
    return () => {
      unSubscribe()
    }
  }, [store])


  //mapDispatchToProps
  if(typeof mapDispatchToProps === 'function'){
    dispatchProps = mapDispatchToProps(dispatch)
  }else if(typeof mapDispatchToProps === 'object'){
    dispatchProps= bindActionCreator(mapDispatchToProps, dispatch)
  }
  return <WrappedComponent {...props} {...stateProps} {...dispatchProps}/>
}

结合Context API的使用方法实现react-redux的Provide组件: react跨层级传数据 创建context -> provider传递value -> 子组件消费context。

如果为纯函数组件,尝试实现forceUpdate更新视图。

以及hookApi的尝试实现

实现一个简易版rc-field-form

说在前面:之所以以rc-field-form为学习表单验证的库,是因为ant-design基于这个强大表单库实现的,这篇文章参考源码实现整个流程记录下口水文~

储备知识

context API
使用场景:组件之间共享此类值的方法,不需要显式的逐层传递类似props,实现祖代组件向后代组件跨层级传值。
使用方法:创建Context => 获取Provider和Consumer => Provider提供值 => Consumer消费值
消费的三种方式:

  • contextType(只能用在类组件,只能订阅单一的context来源)
  • useContext(只能用在函数组件以及自定义hook中)
  • Consumer
    题外话:Vue中的provide & inject来源于Context,Vue UI库的数据传递也是基于这个api。
    Hook

组件化的拆分

类组件Field包裹目标表单组件

将表单组件的粒度大致拆分为 Field 、Form 以及管理数据流的数据仓库类useForm

import Form, {Field} from "../components/my-rc-field-form/";
export default class MyRCFieldForm extends Component {
 render() {
    return (
      <div>
        <Form
          ref={this.formRef}
         >
          <Field name="username" rules={[nameRules]}>
            <Input placeholder="Username" />
          </Field>
          <Field name="password" rules={[passworRules]}>
            <Input placeholder="Password" />
          </Field>
          <button>Submit</button>
        </Form>
      </div>
    );
  }
}

从最小颗粒度组件入手查看Field组件,Field组件里包含着目标元素input/select/textarea, 组件需要将目标组件变成受控组件,才可以控制 接收 以及 修改 目标组件的数据

export default class Field extends Component {
  getControlled = () => {
      const {name} = this.props
      const {getFieldValue, setFieldsValue} = this.context
      return {
        value: getFieldValue(name),
        onChange: e => {
          const newVal = e.target.value
          setFieldsValue({[name]: newVal})
        }
      }
  }

 render(){
    const {children} = this.props // 目标组件
    const returnChildNode  = React.cloneElement(children, this.getControlled()) // 复制目标组件的引用,获取控制权
    return returnChildNode
  }
}

更新数据

rc库的实现还抽离了数据层,记得Ants Design库常见的 getFieldsValue``setFieldsValue修改和获取表单数据的api吗,继续完善数据仓库的搭建:

class FormStore {
   constructor( props ) {
	this.store = {} // 存储数据
   }
    // get
    getFieldsValue = () => {
      return { ...this.store }
    }
    getFieldValue = ( name ) => {
      return this.store[name]
    }

    // set
    setFieldsValue = (newStore) => {
    //合并数据仓库
      this.store = {
        ...this.store,
        ...newStore,
      }
    }	
    getForm =() =>{
        return {
	    getFieldsValue: this.getFieldsValue,
	    getFieldValue: this.getFieldValue,
	    setFieldsValue: this.setFieldsValue, 
	}
    }
}

更新组件实例

此时数据存储完毕,但是并未与组件关联起来,另外还需要和最表单最根部的组件建立数据仓库连接context。

class FormStore {
   ...
   this.fieldEntities = [] // 存储组件实例
   setFieldEntities = field => {
      this.fieldEntities.push(field)
      return () => {
	      // 取消注册
	      this.fieldEntities = this.fieldEntities.filter(f => 
		      f !== field
	      )
	      delete this.store[field.props.name] // 通过name去标识是哪个目标
      }
  }
  getForm =() =>{
       return {
          ... 
          setFieldEntities: this.setFieldEntities,
       }
}

调用

export default class Field extends Component {
  // 将field变成受控组件
  
  static contextType = FieldContext
  
  // 注册组件实例
  componentDidMount(){
    // 注册 与取消注册成对出现
    this.unRegister = this.context.setFieldEntities(this)
  }
  componentWillUnmount(){
    if(this.unRegister){
      this.unRegister()
    }
  }

  onStoreChange = () => {
    this.forceUpdate();
  };
}

提交表单

获取整个表单所有组件的数据,提供给后续操作例如表单验证等。

export default function Form({children, onFinish, onFinishFailed, form}, ref) {
  const [formInstance] = useForm(form);

  React.useImperativeHandle(ref, () => formInstance);
  // 定义回调函数onFinish, onFinisheFail
  formInstance.setCallbacks({onFinish, onFinishFailed});

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        formInstance.submit();
      }}>
      <FieldContext.Provider value={formInstance}>
        {children}
      </FieldContext.Provider>
    </form>
  );
}

调用

 <Form
        ref={this.formRef}
        onFinish={this.onFinish}
        onFinishFailed={this.onFinishFailed}>
...
</Form>
class FormStore {
  constructor(props) {
      this.callbacks = {};
    }
   setCallbacks = (newCallbacks) => {
      this.callbacks = {
        ...this.callbacks,
        ...newCallbacks,
      };
    };
  getForm = () => {
      ...  
      setCallbacks: this.setCallbacks,
  }
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.