Redux 入坑进阶 - 源码解析

原文链接:https://github.com/ecmadao/Co...
转载请注明出处

本文不涉及redux的使用方法,因此可能更适合使用过redux的玩家翻阅?

预热

redux 函数内部包含了大量柯里化函数以及代码组合思想

柯里化函数(curry

通俗的来讲,可以用一句话概括柯里化函数:返回函数的函数

// example
const funcA = (a) => {
  return const funcB = (b) => {
    return a + b
  }
};

上述的funcA函数接收一个参数,并返回同样接收一个参数的funcB函数。

柯里化函数有什么好处呢?

  • 避免了给一个函数传入大量的参数--我们可以通过柯里化来构建类似上例的函数嵌套,将参数的代入分离开,更有利于调试

  • 降低耦合度和代码冗余,便于复用

举个栗子:

// 已知listA,listB两个Array,都由int组成,需要筛选出两个Array的交集
const listA = [1,2,3,4,5];
const listB = [2,4];

const checkIfDataExist = (list) => {
  return (target) => {
    return list.some(value => value === target)
  };
};
// 调用一次checkIfDataExist函数,并将listA作为参数传入,来构建一个新的函数。
// 而新函数的作用则是:检查传入的参数是否存在于listA里
const ifDataExist = checkIfDataExist(listA);

// 使用新函数来对listB里的每一个元素进行筛选
const intersectionList = listB.filter(value => ifDataExist(value));
console.log(intersectionList); // [2,4]

代码组合(compose

代码组合就像是数学中的结合律:

const compose = (f,g) => {
  return (x) => {
    return f(g(x));
  };
};
// 还可以再简洁点
const compose = (f,g) => (x) => f(g(x));

通过这样函数之间的组合,可以大大增加可读性,效果远大于嵌套一大堆的函数调用,并且我们可以随意更改函数的调用顺序

Redux

combineReducers

// 回顾一下combineReducers的使用格式

// 两个reducer
const todos = (state = INIT.todos,action) => {
  // ....
};
const filterStatus = (state = INIT.filterStatus,action) => {
  // ...
};

const appReducer = combineReducers({
  todos,filterStatus
});

还记得combineReducers的黑魔法吗?即:

  1. 传入的Object参数中,对象的keyvalue所代表的reducer function同名

  2. 各个reducer function的名称和需要传入该reducer的state参数同名

源码标注解读(省略部分):

export default function combineReducers(reducers) {
  // 第一次筛选,参数reducers为Object
  // 筛选掉reducers中不是function的键值对
  var reducerKeys = Object.keys(reducers);
  var finalReducers = {}
  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }

  var finalReducerKeys = Object.keys(finalReducers)

  // 二次筛选,判断reducer中传入的值是否合法(!== undefined)
  // 获取筛选完之后的所有key
  var sanityError
  try {
    // assertReducerSanity函数用于遍历finalReducers中的reducer,检查传入reducer的state是否合法
    assertReducerSanity(finalReducers)
  } catch (e) {
    sanityError = e
  }
  
  // 返回一个function。该方法接收state和action作为参数
  return function combination(state = {},action) {
    // 如果之前的判断reducers中有不法值,则抛出错误
    if (sanityError) {
      throw sanityError
    }
    // 如果不是production环境则抛出warning
    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state,finalReducers,action)
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    var hasChanged = false
    var nextState = {}
    // 遍历所有的key和reducer,分别将reducer对应的key所代表的state,代入到reducer中进行函数调用
    for (var i = 0; i < finalReducerKeys.length; i++) {
      var key = finalReducerKeys[i]
      var reducer = finalReducers[key]
      // 这也就是为什么说combineReducers黑魔法--要求传入的Object参数中,reducer function的名称和要和state同名的原因
      var previousStateForKey = state[key]
      var nextStateForKey = reducer(previousStateForKey,action)
      // 如果reducer返回undefined则抛出错误
      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(key,action)
        throw new Error(errorMessage)
      }
      // 将reducer返回的值填入nextState
      nextState[key] = nextStateForKey
      // 如果任一state有更新则hasChanged为true
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

// 检查传入reducer的state是否合法
function assertReducerSanity(reducers) {
  Object.keys(reducers).forEach(key => {
    var reducer = reducers[key]
    // 遍历全部reducer,并给它传入(undefined,action)
    // 当第一个参数传入undefined时,则为各个reducer定义的默认参数
    var initialState = reducer(undefined,{ type: ActionTypes.INIT })
    
    // ActionTypes.INIT几乎不会被定义,所以会通过switch的default返回reducer的默认参数。如果没有指定默认参数,则返回undefined,抛出错误
    if (typeof initialState === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined during initialization. ` +
        `If the state passed to the reducer is undefined,you must ` +
        `explicitly return the initial state. The initial state may ` +
        `not be undefined.`
      )
    }

    var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
    if (typeof reducer(undefined,{ type }) === 'undefined') {
      throw new Error(
        `Reducer "${key}" returned undefined when probed with a random type. ` +
        `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
        `namespace. They are considered private. Instead,you must return the ` +
        `current state for any unknown actions,unless it is undefined,` +
        `in which case you must return the initial state,regardless of the ` +
        `action type. The initial state may not be undefined.`
      )
    }
  })
}

createStore

// 回顾下使用方法
const store = createStore(reducers,state,enhance);

源码标注解读(省略部分):

// 对于未知的action.type,reducer必须返回默认的参数state。这个ActionTypes.INIT就可以用来监测当reducer传入未知type的action时,返回的state是否合法
export var ActionTypes = {
  INIT: '@@redux/INIT'
}

export default function createStore(reducer,initialState,enhancer) {
  // 检查你的state和enhance参数有没有传反
  if (typeof initialState === 'function' && typeof enhancer === 'undefined') {
    enhancer = initialState
    initialState = undefined
  }
  // 如果有传入合法的enhance,则通过enhancer再调用一次createStore
  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }
    return enhancer(createStore)(reducer,initialState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  var currentReducer = reducer
  var currentState = initialState
  var currentListeners = []
  var nextListeners = currentListeners
  var isDispatching = false // 是否正在分发事件

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  // 我们在action middleware中经常使用的getState()方法,返回当前state
  function getState() {
    return currentState
  }

  // 注册listener,同时返回一个取消事件注册的方法。当调用store.dispatch的时候调用listener
  function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error('Expected listener to be a function.')
    }

    var isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      isSubscribed = false
      // 从nextListeners中去除掉当前listener
      ensureCanMutateNextListeners()
      var index = nextListeners.indexOf(listener)
      nextListeners.splice(index,1)
    }
  }

  // dispatch方法接收的action是个对象,而不是方法。
  // 这个对象实际上就是我们自定义action的返回值,因为dispatch的时候,已经调用过我们的自定义action了,比如 dispatch(addTodo())
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
        'Use custom middleware for async actions.'
      )
    }

    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
        'Have you misspelled a constant?'
      )
    }
    // 调用dispatch的时候只能一个个调用,通过dispatch判断调用的状态
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      isDispatching = true
      currentState = currentReducer(currentState,action)
    } finally {
      isDispatching = false
    }
    // 遍历调用各个linster
    var listeners = currentListeners = nextListeners
    for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }

    return action
  }
  // Replaces the reducer currently used by the store to calculate the state.
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.INIT })
  }
  // 当create store的时候,reducer会接受一个type为ActionTypes.INIT的action,使reducer返回他们默认的state,这样可以快速的形成默认的state的结构
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,subscribe,getState,replaceReducer
  }
}

thunkMiddleware

源码及其简单简直给跪...

// 返回以 dispatch 和 getState 作为参数的action
export default function thunkMiddleware({ dispatch,getState }) {
  return next => action => {
    if (typeof action === 'function') {
      return action(dispatch,getState);
    }

    return next(action);
  };
}

applyMiddleware

先复习下用法:

// usage
import {createStore,applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';

const store = createStore(
      reducers,applyMiddleware(thunkMiddleware)
);

applyMiddleware首先接收thunkMiddleware作为参数,两者组合成为一个新的函数(enhance),之后在createStore内部,因为enhance的存在,将会变成返回enhancer(createStore)(reducer,initialState)

源码标注解读(省略部分):

// 定义一个代码组合的方法
// 传入一些function作为参数,返回其链式调用的形态。例如,
// compose(f,g,h) 最终返回 (...args) => f(g(h(...args)))
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  } else {
    const last = funcs[funcs.length - 1]
    const rest = funcs.slice(0,-1)
    return (...args) => rest.reduceRight((composed,f) => f(composed),last(...args))
  }
}

export default function applyMiddleware(...middlewares) {
  // 最终返回一个以createStore为参数的匿名函数
  // 这个函数返回另一个以reducer,enhancer为参数的匿名函数
  return (createStore) => (reducer,enhancer) => {
    var store = createStore(reducer,enhancer)
    var dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,dispatch: (action) => dispatch(action)
    }
    // 每个 middleware 都以 middlewareAPI 作为参数进行注入,返回一个新的链。此时的返回值相当于调用 thunkMiddleware 返回的函数: (next) => (action) => {} ,接收一个next作为其参数
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    // 并将链代入进 compose 组成一个函数的调用链
    // compose(...chain) 返回形如(...args) => f(g(h(...args))),f/g/h都是chain中的函数对象。
    // 在目前只有 thunkMiddleware 作为 middlewares 参数的情况下,将返回 (next) => (action) => {}
    // 之后以 store.dispatch 作为参数进行注入
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,dispatch
    }
  }
}

一脸懵逼?没关系,来结合实际使用总结一下:

当我们搭配redux-thunk这个库的时候,在redux配合components时,通常这么写

import thunkMiddleware from 'redux-thunk';
import { createStore,applyMiddleware,combineReducer } from 'redux';
import * as reducers from './reducers.js';

const appReducer = combineReducer(reducers);
const store = createStore(appReducer,applyMiddleware(thunkMiddleware));

还记得当createStore收到的参数中有enhance时会怎么做吗?

// createStore.js
if (typeof enhancer !== 'undefined') {
  if (typeof enhancer !== 'function') {
    throw new Error('Expected the enhancer to be a function.')
  }
  return enhancer(createStore)(reducer,initialState)
}

也就是说,会变成下面的情况

applyMiddleware(thunkMiddleware)(createStore)(reducer,initialState)
  • applyMiddleware(thunkMiddleware)
    applyMiddleware接收thunkMiddleware作为参数,返回形如(createStore) => (reducer,enhancer) => {}的函数。

  • applyMiddleware(thunkMiddleware)(createStore)
    以 createStore 作为参数,调用上一步返回的函数(reducer,enhancer) => {}

  • applyMiddleware(thunkMiddleware)(createStore)(reducer,initialState)
    以(reducer,initialState)为参数进行调用。

在这个函数内部,thunkMiddleware被调用,其作用是监测typefunctionaction

因此,如果dispatchaction返回的是一个function,则证明是中间件,则将(dispatch,getState)作为参数代入其中,进行action 内部下一步的操作。否则的话,认为只是一个普通的action,将通过next(也就是dispatch)进一步分发。

也就是说,applyMiddleware(thunkMiddleware)作为enhance,最终起了这样的作用:

dispatch调用的action(例如,dispatch(addNewTodo(todo)))进行检查,如果action在第一次调用之后返回的是function,则将(dispatch,getState)作为参数注入到action返回的方法中,否则就正常对action进行分发,这样一来我们的中间件就完成喽~

因此,当action内部需要获取state,或者需要进行异步操作,在操作完成之后进行事件调用分发的话,我们就可以让action 返回一个以(dispatch,getState)为参数的function而不是通常的Objectenhance就会对其进行检测以便正确的处理。

bindActionCreator

这个方法感觉比较少见,我个人也很少用到

在传统写法下,当我们要把 state 和 action 注入到子组件中时,一般会这么做:

import { connect } from 'react-redux';
import {addTodo,deleteTodo} from './action.js';

class TodoComponect extends Component {
  render() {
    return (
      <ChildComponent 
        deleteTodo={this.props.deleteTodo}
        addTodo={this.props.addTodo}
      />
    )
  }
}

function mapStateToProps(state) {
  return {
    state
  }
}
function mapDispatchToProps(dispatch) {
  return {
    deleteTodo: (id) => {
      dispatch(deleteTodo(id));
    },addTodo: (todo) => {
      dispatch(addTodo(todo));
    }
  }
}
export default connect(mapStateToProps,mapDispatchToProps)(TodoComponect);

使用bindActionCreators可以把 action 转为同名 key 的对象,但使用 dispatch 把每个 action 包围起来调用

惟一使用 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它。

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import {addTodo,deleteTodo} as TodoActions from './action.js';

class TodoComponect extends React.Component {
  
  // 在本组件内的应用
  addTodo(todo) {
    let action = TodoActions.addTodo(todo);
    this.props.dispatch(action);
  }
  
  deleteTodo(id) {
    let action = TodoActions.deleteTodo(id);
    this.props.dispatch(action);
  }
  
  render() {
    let dispatch = this.props.dispatch;
    // 传递给子组件
    let boundActionCreators = bindActionCreators(TodoActions,dispatch);
    return (
      <ChildComponent 
        {...boundActionCreators}
      />
    )
  }
}

function mapStateToProps(state) {
  return {
    state
  }
}
export default connect(mapStateToProps)(TodoComponect)

bindActionCreator源码解析

function bindActionCreator(actionCreator,dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

// bindActionCreators期待一个Object作为actionCreators传入,里面是 key: action
export default function bindActionCreators(actionCreators,dispatch) {
  // 如果只是传入一个action,则通过bindActionCreator返回被绑定到dispatch的函数
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators,dispatch)
  }

  if (typeof actionCreators !== 'object' || actionCreators === null) {
    throw new Error(
      `bindActionCreators expected an object or a function,instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
      `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
    )
  }

  // 遍历并通过bindActionCreator分发绑定至dispatch
  var keys = Object.keys(actionCreators)
  var boundActionCreators = {}
  for (var i = 0; i < keys.length; i++) {
    var key = keys[i]
    var actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator,dispatch)
    }
  }
  return boundActionCreators
}

react-redux

Provider

export default class Provider extends Component {
  getChildContext() {
    // 将其声明为 context 的属性之一
    return { store: this.store }
  }

  constructor(props,context) {
    super(props,context)
    // 接收 redux 的 store 作为 props
    this.store = props.store
  }

  render() {
    return Children.only(this.props.children)
  }
}

if (process.env.NODE_ENV !== 'production') {
  Provider.prototype.componentWillReceiveProps = function (nextProps) {
    const { store } = this
    const { store: nextStore } = nextProps

    if (store !== nextStore) {
      warnAboutReceivingStore()
    }
  }
}

Provider.propTypes = {
  store: storeShape.isRequired,children: PropTypes.element.isRequired
}
Provider.childContextTypes = {
  store: storeShape.isRequired
}

connect

传入mapStateToProps,mapDispatchToProps,mergeProps,options
首先获取传入的参数,如果没有则以默认值代替

const defaultMapStateToProps = state => ({}) // eslint-disable-line no-unused-vars
const defaultMapDispatchToProps = dispatch => ({ dispatch })
const { pure = true,withRef = false } = options

之后,通过

const finalMergeProps = mergeProps || defaultMergeProps

选择合并stateProps,dispatchProps,parentProps的方式,默认的合并方式 defaultMergeProps 为:

const defaultMergeProps = (stateProps,dispatchProps,parentProps) => ({
  ...parentProps,...stateProps,...dispatchProps
})

返回一个以 Component 作为参数的函数。在这个函数内部,生成了一个叫做Connect的 Component

// ...
  return function wrapWithConnect(WrappedComponent) {
    const connectDisplayName = `Connect(${getDisplayName(WrappedComponent)})`
    // 检查参数合法性
    function checkStateShape(props,methodName) {}
    // 合并props
    function computeMergedProps(stateProps,parentProps) {
      const mergedProps = finalMergeProps(stateProps,parentProps)
      if (process.env.NODE_ENV !== 'production') {
        checkStateShape(mergedProps,'mergeProps')
      }
      return mergedProps
    }
    
    // start of Connect
    class Connect extends Component {
      constructor(props,context) {
        super(props,context);
        this.store = props.store || context.store
        
        const storeState = this.store.getState()
        this.state = { storeState }
        this.clearCache()
      }
      
      computeStateProps(store,props) {
        // 调用configureFinalMapState,使用传入的mapStateToProps方法(或默认方法),将state map进props
      }
      configureFinalMapState(store,props) {}
      
      computeDispatchProps(store,props) {
        // 调用configureFinalMapDispatch,使用传入的mapDispatchToProps方法(或默认方法),将action使用dispatch封装map进props
      }
      configureFinalMapDispatch(store,props) {}
      
      // 判断是否更新props
      updateStatePropsIfNeeded() {}
      updateDispatchPropsIfNeeded() {}
      updateMergedPropsIfNeeded() {}
      
      componentDidMount() {
        // 内部调用this.store.subscribe(this.handleChange.bind(this))
        this.trySubscribe()
      }
      handleChange() {
        const storeState = this.store.getState()
        const prevStoreState = this.state.storeState
        // 对数据进行监听,发送改变时调用
        this.setState({ storeState })
      }
      
      // 取消监听,清除缓存
      componentWillUnmount() {
        this.tryUnsubscribe()
        this.clearCache()
      }
      
      render() {
        this.renderedElement = createElement(WrappedComponent,this.mergedProps
        )
        return this.renderedElement
      }
    }
    // end of Connect
    
    Connect.displayName = connectDisplayName
    Connect.WrappedComponent = WrappedComponent
    Connect.contextTypes = {
      store: storeShape
    }
    Connect.propTypes = {
      store: storeShape
    }
    
    return hoistStatics(Connect,WrappedComponent)
  }
// ...

我们看见,在connect的最后,返回了使用hoistStatics包装的ConnectWrappedComponent

hoistStatics是什么鬼?为什么使用它?

Copies non-react specific statics from a child component to a parent component. Similar to Object.assign,but with React static keywords blacklisted from being overridden.

也就是说,它类似于Object.assign,作用是将子组件中的 static 方法复制进父组件,但不会覆盖组件中的关键字方法(如 componentDidMount)

import hoistNonReactStatic from 'hoist-non-react-statics';

hoistNonReactStatic(targetComponent,sourceComponent);

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom