深入研究React中setState源码


Posted in Javascript onNovember 17, 2017

React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定。修改数据的同时,可以实现View的刷新。这大大简化了我们的逻辑,只用关心数据流的变化,同时减少了代码量,使得后期维护也更加方便。这个特性则要归功于setState()方法。React中利用队列机制来管理state,避免了很多重复的View刷新。下面我们来从源码角度探寻下setState机制。

1 还是先声明一个组件,从最开始一步步来寻源;

class App extends Component {
  //只在组件重新加载的时候执行一次
  constructor(props) {
    super(props);
   //..
  }
   //other methods
}
//ReactBaseClasses.js中如下:这里就是setState函数的来源;
//super其实就是下面这个函数
function ReactComponent(props, context, updater) {
 this.props = props;
 this.context = context;
 this.refs = emptyObject;
 // We initialize the default updater but the real one gets injected by the
 // renderer.
 this.updater = updater || ReactNoopUpdateQueue;
}
ReactComponent.prototype.setState = function (partialState, callback) {
 this.updater.enqueueSetState(this, partialState);
 if (callback) {
  this.updater.enqueueCallback(this, callback, 'setState');
 }
};

所以主要来看是否传入了updater参数,也就是说何时进行 new 组件;具体的updater参数是怎么传递进来的,以及是那个对象,参见

react源码分析系列文章下面的react中context updater到底是如何传递的

这里直接说结果,updater对象其实就是ReactUpdateQueue.js 中暴漏出的ReactUpdateQueue对象;

2 既然找到了setState之后执行的动作,我们在一步步深入进去

class Root extends React.Component {
 constructor(props) {
  super(props);
  this.state = {
   count: 0
  };
 }
 componentDidMount() {
  let me = this;
  me.setState({
   count: me.state.count + 1
  });
  console.log(me.state.count);  // 打印出0
  me.setState({
   count: me.state.count + 1
  });
  console.log(me.state.count);  // 打印出0
  setTimeout(function(){
   me.setState({
    count: me.state.count + 1
   });
   console.log(me.state.count);  // 打印出2
  }, 0);
  setTimeout(function(){
   me.setState({
    count: me.state.count + 1
   });
   console.log(me.state.count);  // 打印出3
  }, 0);
 }
 render() {
  return (
   <h1>{this.state.count}</h1>
  )
 }
}

ReactComponent.prototype.setState = function (partialState, callback) {
 this.updater.enqueueSetState(this, partialState);
 if (callback) {
  this.updater.enqueueCallback(this, callback, 'setState');
 }
};

ReactUpdateQueue.js

var ReactUpdates = require('./ReactUpdates');

function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
};
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
 //在ReactCompositeComponent.js中有这样一行代码,这就是其来源;
 // Store a reference from the instance back to the internal representation
  //ReactInstanceMap.set(inst, this);
 var internalInstance = ReactInstanceMap.get(publicInstance);
 //返回的是在ReactCompositeComponent.js中construct函数返回的对象;ReactInstance实例对象并不是简单的new 我们写的组件的实例对象,而是经过instantiateReactComponent.js中ReactCompositeComponentWrapper函数包装的对象;详见 创建React组件方式以及源码分析.md
 return internalInstance;
};
var ReactUpdateQueue = {
//。。。。省略其他代码
 enqueueCallback: function (publicInstance, callback, callerName) {
  ReactUpdateQueue.validateCallback(callback, callerName);
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
  if (!internalInstance) {
   return null;
  }
//这里将callback放入组件实例的_pendingCallbacks数组中;
  if (internalInstance._pendingCallbacks) {
   internalInstance._pendingCallbacks.push(callback);
  } else {
   internalInstance._pendingCallbacks = [callback];
  }
  // TODO: The callback here is ignored when setState is called from
  // componentWillMount. Either fix it or disallow doing so completely in
  // favor of getInitialState. Alternatively, we can disallow
  // componentWillMount during server-side rendering.
  enqueueUpdate(internalInstance);
 },

 enqueueSetState: function (publicInstance, partialState) {
  var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
  if (!internalInstance) {
   return;
  }
  //这里,初始化queue变量,同时初始化 internalInstance._pendingStateQueue = [ ] ;
  //对于 || 的短路运算还是要多梳理下
  //queue数组(模拟队列)中存放着setState放进来的对象;
  var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
  //这里将partialState放入queue数组中,也就是internalInstance._pendingStateQueue 数组中,此时,每次setState的partialState,都放进了React组件实例对象上的_pendingStateQueue属性中,成为一个数组;
  queue.push(partialState);

  enqueueUpdate(internalInstance);
 },
};

module.exports = ReactUpdateQueue;

可以看到enqueueSetState enqueueCallback 最后都会执行enqueueUpdate;

function enqueueUpdate(internalInstance) {
 ReactUpdates.enqueueUpdate(internalInstance);
}

ReactUpdates.js

var dirtyComponents = [];
var updateBatchNumber = 0;
var asapCallbackQueue = CallbackQueue.getPooled();
var asapEnqueued = false;
//这里声明batchingStrategy为null,后期通过注册给其赋值;
var batchingStrategy = null;
//这里的component参数是js中ReactCompositeComponentWrapper函数包装的后的React组件实例对象;
function enqueueUpdate(component) {
 ensureInjected();
//第一次执行setState的时候,可以进入if语句,遇到里面的return语句,终止执行
 //如果不是正处于创建或更新组件阶段,则处理update事务

 if (!batchingStrategy.isBatchingUpdates) {
  //batchedUpdates就是ReactDefaultBatchingStrategy.js中声明的
  batchingStrategy.batchedUpdates(enqueueUpdate, component);
  return;
 }
//第二次执行setState的时候,进入不了if语句,将组件放入dirtyComponents
 //如果正在创建或更新组件,则暂且先不处理update,只是将组件放在dirtyComponents数组中

 dirtyComponents.push(component);
 if (component._updateBatchNumber == null) {
  component._updateBatchNumber = updateBatchNumber + 1;
 }
};
//enqueueUpdate包含了React避免重复render的逻辑。mountComponent和updateComponent方法在执行的最开始,会调用到batchedUpdates进行批处理更新,此时会将isBatchingUpdates设置为true,也就是将状态标记为现在正处于更新阶段了。之后React以事务的方式处理组件update,事务处理完后会调用wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES这个wrapper,故最终会调用RESET_BATCHED_UPDATES.close(), 它最终会将isBatchingUpdates设置为false。

ReactDefaultBatchingStrategy.js

//RESET_BATCHED_UPDATES用来管理isBatchingUpdates的状态
var RESET_BATCHED_UPDATES = {
 initialize: emptyFunction,
 close: function () {
  // 事务批更新处理结束时,将isBatchingUpdates设为了false
  ReactDefaultBatchingStrategy.isBatchingUpdates = false;
 }
};
//FLUSH_BATCHED_UPDATES会在一个transaction的close阶段运行runBatchedUpdates,从而执行update。
//因为close的执行顺序是FLUSH_BATCHED_UPDATES.close ==> 然后RESET_BATCHED_UPDATES.close
var FLUSH_BATCHED_UPDATES = {
 initialize: emptyFunction,
 close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];

function ReactDefaultBatchingStrategyTransaction() {
 this.reinitializeTransaction();
}

_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
 getTransactionWrappers: function () {
  return TRANSACTION_WRAPPERS;
 }
});
//这个transition就是下面ReactDefaultBatchingStrategy对象中使用的transaction变量
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
 isBatchingUpdates: false,

 /**
  * Call the provided function in a context within which calls to `setState`
  * and friends are batched such that components aren't updated unnecessarily.
  */
 batchedUpdates: function (callback, a, b, c, d, e) {
  var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
// 批处理最开始时,将isBatchingUpdates设为true,表明正在更新
  ReactDefaultBatchingStrategy.isBatchingUpdates = true;

  // The code is written this way to avoid extra allocations
  if (alreadyBatchingUpdates) {
   return callback(a, b, c, d, e);
  } else {
   //transition在上面已经声明; // 以事务的方式处理updates,后面详细分析transaction
   return transaction.perform(callback, null, a, b, c, d, e);
  }
 }
};

module.exports = ReactDefaultBatchingStrategy;

接下来我们看下React中的事物处理机制到底是如何运行的;

Transaction.js

var _prodInvariant = require('./reactProdInvariant');
var invariant = require('fbjs/lib/invariant');
var OBSERVED_ERROR = {};
var TransactionImpl = {
 reinitializeTransaction: function () {
  //getTransactionWrappers这个函数ReactDefaultBatchingStrategy.js中声明的,上面有;返回一个数组;
  this.transactionWrappers = this.getTransactionWrappers();
  if (this.wrapperInitData) {
   this.wrapperInitData.length = 0;
  } else {
   this.wrapperInitData = [];
  }
  this._isInTransaction = false;
 },

 _isInTransaction: false,
 getTransactionWrappers: null,
 isInTransaction: function () {
  return !!this._isInTransaction;
 },
 perform: function (method, scope, a, b, c, d, e, f) {
  var errorThrown;
  var ret;
  try {
   this._isInTransaction = true;
   errorThrown = true;
   //var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
   //1 这里会先执行所有的TRANSACTION_WRAPPERS中成员的initialize方法,上面声明的其都是emptyFunction
   this.initializeAll(0);
   //2 这里其实还是执行的 enqueueUpdate 函数
   ret = method.call(scope, a, b, c, d, e, f);
   errorThrown = false;
  } finally {
   try {
    if (errorThrown) {
     // If `method` throws, prefer to show that stack trace over any thrown
     // by invoking `closeAll`.
     try {
      this.closeAll(0);
     } catch (err) {}
    } else {
     // Since `method` didn't throw, we don't want to silence the exception
     // here.
     //3 执行TRANSACTION_WRAPPERS对象中成员的所有close方法;
     this.closeAll(0);
    }
   } finally {
    this._isInTransaction = false;
   }
  }
  return ret;
 },

 initializeAll: function (startIndex) {
  var transactionWrappers = this.transactionWrappers;
  for (var i = startIndex; i < transactionWrappers.length; i++) {
   var wrapper = transactionWrappers[i];
   try {
    
    this.wrapperInitData[i] = OBSERVED_ERROR;
    this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
   } finally {
    if (this.wrapperInitData[i] === OBSERVED_ERROR) {
     
     try {
      this.initializeAll(i + 1);
     } catch (err) {}
    }
   }
  }
 },
 closeAll: function (startIndex) {
  var transactionWrappers = this.transactionWrappers;
  for (var i = startIndex; i < transactionWrappers.length; i++) {
   var wrapper = transactionWrappers[i];
   var initData = this.wrapperInitData[i];
   var errorThrown;
   try {
  
    errorThrown = true;
    if (initData !== OBSERVED_ERROR && wrapper.close) {
     wrapper.close.call(this, initData);
    }
    errorThrown = false;
   } finally {
    if (errorThrown) {
    
     try {
      this.closeAll(i + 1);
     } catch (e) {}
    }
   }
  }
  this.wrapperInitData.length = 0;
 }
};

module.exports = TransactionImpl

//3 执行TRANSACTION_WRAPPERS对象中成员的所有close方法;
var FLUSH_BATCHED_UPDATES = {
 initialize: emptyFunction,
 close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};

接着会执行ReactUpdates.js中的flushBatchedUpdates方法

ReactUpdates.js中

var flushBatchedUpdates = function () {
 
 while (dirtyComponents.length || asapEnqueued) {
  if (dirtyComponents.length) {
   var transaction = ReactUpdatesFlushTransaction.getPooled();
   //这里执行runBatchedUpdates函数;
   transaction.perform(runBatchedUpdates, null, transaction);
   ReactUpdatesFlushTransaction.release(transaction);
  }

  if (asapEnqueued) {
   asapEnqueued = false;
   var queue = asapCallbackQueue;
   asapCallbackQueue = CallbackQueue.getPooled();
   queue.notifyAll();
   CallbackQueue.release(queue);
  }
 }
};
function runBatchedUpdates(transaction) {
 var len = transaction.dirtyComponentsLength;
 
 dirtyComponents.sort(mountOrderComparator);

 updateBatchNumber++;

 for (var i = 0; i < len; i++) {
 
  var component = dirtyComponents[i];
  var callbacks = component._pendingCallbacks;
  component._pendingCallbacks = null;

  var markerName;
  if (ReactFeatureFlags.logTopLevelRenders) {
   var namedComponent = component;
   // Duck type TopLevelWrapper. This is probably always true.
   if (component._currentElement.type.isReactTopLevelWrapper) {
    namedComponent = component._renderedComponent;
   }
   markerName = 'React update: ' + namedComponent.getName();
   console.time(markerName);
  }
//这里才是真正的开始更新组件
  ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);

  if (markerName) {
   console.timeEnd(markerName);
  }

  if (callbacks) {
   for (var j = 0; j < callbacks.length; j++) {
    transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
   }
  }
 }
}

ReactReconciler.js中

performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
  if (internalInstance._updateBatchNumber !== updateBatchNumber) {
   // The component's enqueued batch number should always be the current
   // batch or the following one.
   return;
  }
 //这里执行React组件实例对象的更新;internalInstance上的performUpdateIfNecessary在ReactCompositeComponent.js中的;
  internalInstance.performUpdateIfNecessary(transaction);
  if (process.env.NODE_ENV !== 'production') {
   if (internalInstance._debugID !== 0) {
    ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
   }
  }
 }

ReactCompositeComponent.js

performUpdateIfNecessary: function (transaction) {
 if (this._pendingElement != null) {
  // receiveComponent会最终调用到updateComponent,从而刷新View
  ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
 } else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
  // 执行updateComponent,从而刷新View。
  this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
 } else {
  this._updateBatchNumber = null;
 }
},

 //执行更新React组件的props. state。context函数

 updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
  var inst = this._instance;
  var willReceive = false;
  var nextContext;
  // Determine if the context has changed or not
  if (this._context === nextUnmaskedContext) {
   nextContext = inst.context;
  } else {
   nextContext = this._processContext(nextUnmaskedContext);
   willReceive = true;
  }

  var prevProps = prevParentElement.props;
  var nextProps = nextParentElement.props;

  // Not a simple state update but a props update
  if (prevParentElement !== nextParentElement) {
   willReceive = true;
  }

  // An update here will schedule an update but immediately set
  // _pendingStateQueue which will ensure that any state updates gets
  // immediately reconciled instead of waiting for the next batch.
  if (willReceive && inst.componentWillReceiveProps) {
   if (process.env.NODE_ENV !== 'production') {
    measureLifeCyclePerf(function () {
     return inst.componentWillReceiveProps(nextProps, nextContext);
    }, this._debugID, 'componentWillReceiveProps');
   } else {
    inst.componentWillReceiveProps(nextProps, nextContext);
   }
  }
//这里可以知道为什么setState可以接受函数,主要就是_processPendingState函数;
  //这里仅仅是将每次setState放入到_pendingStateQueue队列中的值,合并到nextState,并没有真正的更新state的值;真正更新组件的state的值是在下面;
  var nextState = this._processPendingState(nextProps, nextContext);
  var shouldUpdate = true;

  if (!this._pendingForceUpdate) {
   if (inst.shouldComponentUpdate) {
    if (process.env.NODE_ENV !== 'production') {
     shouldUpdate = measureLifeCyclePerf(function () {
      return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
     }, this._debugID, 'shouldComponentUpdate');
    } else {
     shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
    }
   } else {
    if (this._compositeType === CompositeTypes.PureClass) {
     shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
    }
   }
  }

  this._updateBatchNumber = null;
  if (shouldUpdate) {
   this._pendingForceUpdate = false;
   // Will set `this.props`, `this.state` and `this.context`.
   this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
  } else {
   // If it's determined that a component should not update, we still want
   // to set props and state but we shortcut the rest of the update.
   //诺:在这里更新组件的state. props 等值;
   this._currentElement = nextParentElement;
   this._context = nextUnmaskedContext;
   inst.props = nextProps;
   inst.state = nextState;
   inst.context = nextContext;
  }
 },


_processPendingState: function (props, context) {
 var inst = this._instance;
 var queue = this._pendingStateQueue;
 var replace = this._pendingReplaceState;
 this._pendingReplaceState = false;
 this._pendingStateQueue = null;

 if (!queue) {
  return inst.state;
 }

 if (replace && queue.length === 1) {
  return queue[0];
 }

 var nextState = _assign({}, replace ? queue[0] : inst.state);
 for (var i = replace ? 1 : 0; i < queue.length; i++) {
  var partial = queue[i];
  //如果是setState的参数是一个函数,那么该函数接受三个参数,分别是state props context
  _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
 }

 return nextState;
},

this.state的更新会在_processPendingState执行完执行。所以两次setState取到的都是this.state.count最初的值0,这就解释了之前的现象。其实,这也是React为了解决这种前后state依赖但是state又没及时更新的一种方案,因此在使用时大家要根据实际情况来判断该用哪种方式传参。来看个小例子直观感受下

handleClickOnLikeButton () {
  this.setState({ count: 0 }) // => this.state.count 还是 undefined
  this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
  this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
 }
//....VS ....
handleClickOnLikeButton () {
  this.setState((prevState) => {
   return { count: 0 }
  })
  this.setState((prevState) => {
   return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
  })
  this.setState((prevState) => {
   return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
  })
  // 最后的结果是 this.state.count 为 3
 }
...

setState流程还是很复杂的,设计也很精巧,避免了重复无谓的刷新组件。它的主要流程如下

  1. enqueueSetState将state放入队列中,并调用enqueueUpdate处理要更新的Component
  2. 如果组件当前正处于update事务中,则先将Component存入dirtyComponent中。否则调用batchedUpdates处理。
  3. batchedUpdates发起一次transaction.perform()事务
  4. 开始执行事务初始化,运行,结束三个阶段
    1. 初始化:事务初始化阶段没有注册方法,故无方法要执行
    2. 运行:执行setSate时传入的callback方法,一般不会传callback参数
    3. 结束:更新isBatchingUpdates为false,并执行FLUSH_BATCHED_UPDATES这个wrapper中的close方法
  5. FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的dirtyComponents,调用updateComponent刷新组件,并执行它的pendingCallbacks, 也就是setState中设置的callback。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js实现点小图看大图效果的思路及示例代码
Oct 28 Javascript
jquery提交form表单简单示例分享
Mar 03 Javascript
JavaScript获取flash对象与网上的有所不同
Apr 21 Javascript
jQuery中借助deferred来请求及判断AJAX加载的实例讲解
May 24 Javascript
DWR3 访问WEB元素的两种方法实例详解
Jan 03 Javascript
使用base64对图片的二进制进行编码并用ajax进行显示
Jan 03 Javascript
vue数据双向绑定原理解析(get &amp; set)
Mar 08 Javascript
js实现时间轴自动排列效果
Mar 09 Javascript
原生js实现验证码功能
Mar 16 Javascript
vue实现div拖拽互换位置
Jul 29 Javascript
小程序实现背景音乐播放和暂停
Jun 19 Javascript
如何使用JavaScript实现无缝滚动自动播放轮播图效果
Aug 20 Javascript
Vue.js在数组中插入重复数据的实现代码
Nov 17 #Javascript
jQuery实现滚动效果
Nov 17 #jQuery
不使用 JS 匿名函数理由
Nov 17 #Javascript
Vue-cli-webpack搭建斗鱼直播步骤详解
Nov 17 #Javascript
浅谈Vue.js 组件中的v-on绑定自定义事件理解
Nov 17 #Javascript
bootstrap响应式导航条模板使用详解(含下拉菜单,弹出框)
Nov 17 #Javascript
浅析node Async异步处理模块用例分析及常用方法介绍
Nov 17 #Javascript
You might like
完美解决:Apache启动问题―(OS 10022)提供了一个无效的参数
2013/06/08 PHP
PHP在线书签系统分享
2016/01/04 PHP
PHP编程获取图片的主色调的方法【基于Imagick扩展】
2017/08/02 PHP
phpStudy配置多站点多域名和多端口的方法
2017/09/01 PHP
php往mysql中批量插入数据实例教程
2018/12/12 PHP
可以支持多中格式的JS键盘
2007/05/02 Javascript
jQuery 设置 CSS 属性示例介绍
2014/01/16 Javascript
js判断60秒以及倒计时示例代码
2014/01/24 Javascript
javascript中不提供sleep功能如何实现这个功能
2014/05/27 Javascript
node.js使用require()函数加载模块
2014/11/26 Javascript
解决js页面滚动效果scrollTop在FireFox与Chrome浏览器间的兼容问题的方法
2015/12/03 Javascript
详解JavaScript对象和数组
2015/12/03 Javascript
详解Webwork中Action 调用的方法
2016/02/02 Javascript
jquery trigger实现联动的方法
2016/02/29 Javascript
js 取消页面可以选中文字的功能方法
2018/01/02 Javascript
vue自定义filters过滤器
2018/04/26 Javascript
vue中v-model的应用及使用详解
2018/06/27 Javascript
Vue中的基础过渡动画及实现原理解析
2018/12/04 Javascript
es6 filter() 数组过滤方法总结
2019/04/03 Javascript
js之切换全屏和退出全屏实现代码实例
2019/09/09 Javascript
深入探索VueJS Scoped CSS 实现原理
2019/09/23 Javascript
JS常见内存泄漏及解决方案解析
2020/05/30 Javascript
Python编写百度贴吧的简单爬虫
2015/04/02 Python
Ubuntu16.04/树莓派Python3+opencv配置教程(分享)
2018/04/02 Python
python分布式编程实现过程解析
2019/11/08 Python
Python中常用的高阶函数实例详解
2020/02/21 Python
Django 实现将图片转为Base64,然后使用json传输
2020/03/27 Python
python将YUV420P文件转PNG图片格式的两种方法
2021/01/22 Python
List、Map、Set三个接口,存取元素时,各有什么特点?
2015/09/27 面试题
《临死前的严监生》教学反思
2014/02/13 职场文书
安踏广告词改编版
2014/03/21 职场文书
2016党员党章学习心得体会
2016/01/14 职场文书
党员干部学法用法心得体会
2016/01/21 职场文书
感恩信:写给爸爸妈妈的一封感谢信
2019/09/12 职场文书
MySQL pt-slave-restart工具的使用简介
2021/04/07 MySQL
JS函数式编程实现XDM一
2022/06/16 Javascript