做选择网站,软件服务网站设计费如何做分录,天津模板建站哪家好,免费搭建个人业务网站说说React render方法的原理#xff1f;在什么时候会被触发#xff1f;
render函数里面可以编写JSX#xff0c;转化成createElement这种形式#xff0c;用于生成虚拟DOM#xff0c;最终转化成真实DOM
在 React 中#xff0c;类组件只要执行了 setState 方法#xff0c…说说React render方法的原理在什么时候会被触发
render函数里面可以编写JSX转化成createElement这种形式用于生成虚拟DOM最终转化成真实DOM
在 React 中类组件只要执行了 setState 方法就一定会触发 render 函数执行函数组件使用useState更改状态不一定导致重新render
组件的 props 改变了不一定触发 render 函数的执行但是如果 props 的值来自于父组件或者祖先组件的 state
在这种情况下父组件或者祖先组件的 state 发生了改变就会导致子组件的重新渲染
所以一旦执行了setState就会执行render方法useState 会判断当前值有无发生改变确定是否执行render方法一旦父组件发生渲染子组件也会渲染
说说React事件和原生事件的执行顺序
为什么要有合成事件
在传统的事件里不同的浏览器需要兼容不同的写法在合成事件中React提供统一的事件对象抹平了浏览器的兼容性差异React通过顶层监听的形式通过事件委托的方式来统一管理所有的事件可以在事件上区分事件优先级优化用户体验
React在合成事件上对于16版本和17版本的合成事件有很大不同我们也会简单聊聊区别。
总结
16版本先执行原生事件当冒泡到document时统一执行合成事件17版本在原生事件执行前先执行合成事件捕获阶段原生事件执行完毕执行冒泡阶段的合成事件,通过根节点来管理所有的事件
原生的阻止事件流会阻断合成事件的执行合成事件阻止后也会影响到后续的原生执行
说说对受控组件和非受控组件的理解以及应用场景 受控组件 受控组件是指组件的状态由 React 组件的 state 控制并通过 props 传递给子组件子组件通过回调函数来更新父组件的状态。在受控组件中数据流是单向的父组件通过 props 将状态传递给子组件子组件通过回调函数将事件传递回父组件。应用场景适用于需要实时控制和监控组件状态变化的情况例如表单输入框、复选框等需要即时响应用户输入的组件。 非受控组件 非受控组件是指组件的状态不受 React state 控制而是由 DOM 元素本身维护状态React 只负责渲染。在非受控组件中组件的状态由 DOM 元素自身管理React 只负责初始化组件和处理生命周期方法。应用场景适用于简单的交互组件不需要频繁地进行状态管理和更新的情况可以减少一些状态管理的代码量例如一些仅需初始化时设置数值的组件。
说说你对immutable的理解如何应用在react项目中
Immutable 是指数据不可变一旦创建之后就不能被修改。在 JavaScript 中原始类型如字符串、数字是不可变的而对象和数组是可变的。 性能优化使用 Immutable 数据结构可以帮助 React 进行浅比较提高组件更新时的性能。通过比较引用而不是深层次比较对象内容可以减少不必要的重新渲染。 简化状态管理不可变数据结构可以减少状态管理的复杂性。在 Redux 或者其他状态管理工具中配合 Immutable 数据结构可以更容易地追踪状态的变化并且可以避免直接修改状态而导致的副作用。 防止意外修改不可变数据结构可以减少由于在多处修改同一数据而引发的 bug。在 React 组件中通过使用 Immutable 数据结构可以更容易地追踪数据的变化从而减少意外的状态修改。
在 React 项目中可以通过以下方式应用 Immutable 数据结构
使用 Immutable.jsImmutable.js 是一个专门为 JavaScript 提供不可变数据结构的库可以使用其中的 Map、List 等数据结构来管理 React 组件的状态。结合 Redux 或者 Context API在使用 Redux 或者 React 的 Context API 进行状态管理时可以结合 Immutable 数据结构来管理状态以提高状态管理的效率和可维护性。
说说React Jsx转换成真实DOM过程 JSX 编写阶段 在编写 React 组件时可以使用 JSX 语法来描述组件的结构和内容。 JSX 转换阶段 在构建过程中使用 Babel 或其他工具将 JSX 转换成普通的 JavaScript 代码。JSX 中的标签会被转换成对应的 React.createElement 函数调用。JSX 中的属性会被转换成一个对象作为 createElement 函数的第二个参数传递。JSX 中的子元素会被转换成 createElement 函数的额外参数作为子元素传递。 React.createElement 函数调用 在转换后的代码中每个 JSX 标签都会被转换成对应的 React.createElement 函数调用。React.createElement 接收三个参数标签名字符串或函数、属性对象和子元素。React.createElement 函数会返回一个描述该元素的对象称为 React 元素React Element。 虚拟 DOM 构建阶段 在执行 React.createElement 函数时会创建一个描述组件结构的虚拟 DOM 对象Virtual DOM。 虚拟 DOM 渲染阶段 当组件准备好进行渲染时React 会将虚拟 DOM 进行比对并计算出需要进行更新的部分。 真实 DOM 更新阶段 在虚拟 DOM 渲染阶段结束后React 会将需要更新的部分转换为真实 DOM 操作。
说说你在React项目是如何捕获错误的
在 React 项目中采用错误边界、全局错误处理、组件生命周期方法、try...catch 等方式结合起来可以有效地捕获和处理错误提高应用的稳定性和用户体验。同时及时记录和监控错误信息也是保障项目质量的重要手段。 错误边界Error Boundaries 使用 React 的错误边界是一种捕获组件树中 JavaScript 错误的方式。通过定义错误边界组件在组件树中将错误限制在边界内避免整个应用崩溃。在类组件中可以通过 componentDidCatch 钩子函数捕获组件生命周期中的错误。在函数式组件中可以使用 ErrorBoundary 组件包裹需要捕获错误的部分。
说说React服务端渲染怎么做原理是什么 服务器端渲染阶段 当请求到达服务器时服务器会根据请求的 URL 路径找到对应的 React 组件并调用相应的生命周期方法或函数组件。服务器使用 ReactDOMServer 提供的 renderToString 或 renderToNodeStream 方法将组件渲染成一个字符串或流。 HTML 响应生成阶段 服务器将渲染后的字符串或流包装在一个 HTML 模板中并返回给客户端。这个 HTML 模板通常包括渲染后的组件内容、样式和其他静态资源的链接。 客户端激活阶段 客户端接收到 HTML 响应后会解析 HTML 文档并重新创建 React 组件树。在客户端重新创建组件时React 会尽量复用服务器端生成的组件实例以节省计算和网络开销。客户端会在组件挂载后继续处理后续的交互和更新。
React Fiber 是如何实现更新过程可控 可中断性React Fiber 将整个组件更新过程分解为多个可中断的小任务单元称为 fiber。这些 fiber 可以根据优先级和时间片来进行调度使得 React 能够在更新过程中灵活地中断、暂停和恢复任务从而提高用户界面的响应性。 优先级调度每个 fiber 都有自己的优先级React Fiber 根据优先级动态调整任务的执行顺序确保高优先级任务能够优先执行从而实现对更新过程的控制。 协调策略在更新过程中会构建新的虚拟 DOM 树并与旧的虚拟 DOM 树进行比较确定需要更新的部分最终只对需要更新的部分进行实际 DOM 操作。 增量更新React Fiber 使用增量更新的方式将更新过程分解为多个阶段通过差异对比和增量更新的方式只更新发生变化的部分避免全量重新渲染提高了性能和效率。
setState 是同步还是异步的
setState 方法既可以是同步的也可以是异步的具体取决于使用 setState 的地方和上下文。 同步更新当在生命周期方法如 componentDidMount、componentDidUpdate和原生事件处理函数中调用 setState 时通常会同步更新状态。这是因为 React 可以立即执行状态更新并重新渲染组件。 批量异步更新在大多数情况下setState 是异步的即在事件处理函数、异步回调或 setTimeout 等代码块中调用 setState 时React 会将多个 setState 调用合并成一个批量更新操作以提高性能。这意味着连续的 setState 调用不会立即触发组件的重新渲染而是在当前代码块执行完毕后React 根据一定策略进行更新。
简述下 React 的事件代理机制
React 使用了一种称为事件代理的机制来处理组件中的事件。事件代理是一种将事件处理函数绑定到父元素而不是每个子元素的技术它利用事件冒泡机制将事件从子元素传递到父元素进行处理。 事件绑定在 React 组件中可以通过在 JSX 中添加事件监听器来绑定事件处理函数 事件冒泡当用户在子元素上触发事件时该事件会按照从子元素到父元素的顺序依次向上冒泡。也就是说事件会首先在触发事件的子元素上被触发然后沿着组件层级向上冒泡到更高层的父元素。 事件委派React 在组件树的根节点上绑定事件监听器这个根节点可以是最外层的 DOM 元素也可以是某个组件的容器元素。当事件冒泡至根节点时React 会根据事件类型找到对应的事件处理函数并执行相应的处理逻辑。这种方式避免了为每个子元素都绑定事件监听器提高了性能和内存利用率。
简述下 React 的生命周期每个生命周期都做了什么 挂载阶段 constructor()组件的构造函数在组件被创建时调用用于初始化状态和绑定事件处理函数。static getDerivedStateFromProps(props, state)在组件接收到新的 props 或者在组件初始化时调用返回一个对象来更新状态用于替代旧的 componentWillReceiveProps。render()根据组件的状态和属性渲染组件的内容。componentDidMount()组件挂载后调用可以进行 DOM 操作或数据请求等副作用。 更新阶段 static getDerivedStateFromProps(props, state)在组件接收到新的 props 时调用返回一个对象来更新状态。shouldComponentUpdate(nextProps, nextState)在组件接收到新的 props 或 state 时调用用于控制组件是否重新渲染。render()根据新的状态和属性重新渲染组件。getSnapshotBeforeUpdate(prevProps, prevState)在最近一次渲染输出提交到 DOM 上之前调用可以在此保存当前 DOM 的一些信息。componentDidUpdate(prevProps, prevState, snapshot)组件更新完成后调用可以进行 DOM 操作或处理更新前的信息。 卸载阶段 componentWillUnmount()组件卸载前调用用于清理定时器、取消订阅等操作。
为什么不能在循环、条件或嵌套函数中调用 Hooks
react用链表来严格保证hooks的顺序。Hooks 依赖于 React 内部的调用顺序来确定每个 Hook 的对应关系如果在循环、条件语句或嵌套函数中调用 Hooks可能会导致 Hooks 调用顺序发生变化从而破坏 React 内部的依赖关系。这种情况下React 可能无法正确地管理状态造成组件的不稳定行为。
说说你对 React Hook的闭包陷阱的理解有哪些解决方案
常见的 React Hook 闭包陷阱包括
在 useEffect 或 useCallback 中引用了过时的 state 或 props。在自定义 Hook 中使用了闭包来保存状态或引用可能导致多个实例之间状态共享。在循环中使用 Hook可能导致闭包中的变量无法正确捕获循环中的值。
解决 React Hook 的闭包陷阩可以采取以下方案
使用 useEffect 的依赖项数组来确保正确捕获最新的 state 和 props 值避免闭包中引用过时的值。避免在自定义 Hook 中使用闭包来保存状态或引用可以使用 useRef 来保存可变的引用或者通过 useReducer 来管理状态。在循环中使用 Hook 时可以通过将循环变量作为 useEffect 的依赖项或传递给回调函数的方式来正确捕获循环中的值。
React18新特性 Concurrent Mode并发模式React 18 引入了 Concurrent Mode这是一个可选的特性可以帮助优化应用程序的性能和用户体验。通过 Concurrent ModeReact 可以更好地处理大型应用程序中的渲染优先级并在不阻塞用户界面的情况下提高性能。 新的渲染器 RendererReact 18 引入了新的渲染器允许开发人员更好地控制渲染过程并进行更细粒度的优化。 Automatic Batching自动批处理React 18 改进了更新批处理机制使得在某些情况下不再需要手动进行批处理操作从而提高了性能。 新的树形结构 ReconcilerReact 18 中引入了新的树形结构 Reconciler可以更好地处理组件树的更新和渲染提高了整体的性能和效率。 新的事件系统React 18 带来了全新的事件系统使得事件处理更加灵活和高效。开发人员可以更容易地管理和优化事件处理逻辑。
useRef / ref / forwardsRef 的区别是什么?
ref 是 React 提供的一个属性用于在组件中访问子组件或 DOM 元素。每次渲染都会被重新创建。useRef 是一个 Hook 函数用于在函数组件中创建可变的 ref 对象。会因为重新渲染而改变。forwardRef 是一个高阶组件用于在函数组件中向子组件传递 ref。
React 和 Vue 在技术层面有哪些区别 语法和模板 React 使用 JSXJavaScript XML作为组件的声明语法将 HTML 结构和 JavaScript 代码混合在一起。Vue 使用单文件组件SFC的方式将模板、脚本和样式放在同一个文件中使得代码更加模块化和清晰。 数据绑定 React 使用单向数据流父组件通过 props 将数据传递给子组件子组件需要通过回调函数来改变父组件的数据。Vue 使用双向数据绑定通过 v-model 指令可以实现父子组件之间的数据双向绑定简化了数据传递和状态管理。 状态管理 React 可以使用 Context API 或者第三方库如 Redux、MobX来进行状态管理提供了更灵活的状态管理方式。Vue 提供了 Vuex 状态管理库用于集中式存储管理应用的所有组件的状态。 组件通信 React 中组件之间的通信可以通过 props、Context API、回调函数等方式实现。Vue 中组件之间的通信可以通过 props、自定义事件、Vuex 等方式实现。 生命周期 React 组件的生命周期包括挂载、更新和卸载阶段提供了一系列生命周期方法供开发者进行操作。Vue 组件也有生命周期钩子函数但与 React 不同的是Vue 的生命周期钩子函数更细粒度例如 beforeCreate、created、beforeMount、mounted 等。 虚拟 DOM React 使用虚拟 DOM 来提高性能通过比对虚拟 DOM 的差异来最小化页面重新渲染的开销。Vue 同样使用虚拟 DOM但采用了模板编译的方式将模板编译成渲染函数提高了性能。
taro 的实现原理是怎么样的
Taro 利用 Babel、React、Webpack 等技术通过封装原生 API 和提供不同的 Polyfill 实现了多端适配同时也支持复杂的样式表达和自动化导入组件等特性。Taro 的实现原理主要是通过源码转换、组件映射、API 封装和样式处理等方式实现了多端统一开发的目标 单页应用如何提高加载速度
使用代码分割将代码拆分成小块并按需加载懒加载以避免不必要的网络请求和减少加载时间。缓存资源利用浏览器缓存来存储重复使用的文件例如 CSS 和 JS 文件、图片等。预加载关键资源在首次渲染之前先提前加载关键资源例如首页所需的 JS、CSS 或数据以保证关键内容的快速呈现。使用合适的图片格式选择合适的图片格式例如 JPEG、PNG、WebP 等并根据需要进行压缩以减少文件大小。对于一些小图标可以使用 iconfont 等字体文件来代替。启用 Gzip 压缩使用服务器端的 Gzip 压缩算法对文件进行压缩以减少传输时间和带宽消耗。使用 CDN使用内容分发网络CDN来缓存和传递文件以提高文件的下载速度和可靠性。优化 API 请求尽可能地减少 API 调用的数量并使用缓存和延迟加载等技术来优化 API 请求的效率。使用服务器端渲染使用服务器端渲染SSR来生成 HTML以减少客户端渲染所需的时间和资源。但需要注意SSR 也可能增加了服务器的负担并使网站更复杂。
react-router 里的 Link 标签和 a 标签有什么区别 性能优化 Link 组件是 React-Router 提供的组件它在用户点击时会使用 JavaScript 动态地改变 URL 而不会重新加载整个页面从而实现单页应用的导航避免了页面的完全刷新提升了性能和用户体验。a 标签是 HTML 中的超链接标签在用户点击时会导致整个页面重新加载或跳转到新的页面这种方式会导致页面的重复加载和渲染影响性能。 路由管理 Link 组件与 React-Router 的路由管理机制结合可以在单页应用中实现路由的切换、动态参数传递等功能同时保持页面状态的稳定。a 标签通常用于传统的多页应用或简单的静态页面点击后会直接加载新的页面或跳转到指定链接。 样式处理 使用 Link 组件可以方便地进行样式控制通过设置 activeClassName 等属性可以实现当前链接高亮显示等效果。使用 a 标签需要自行处理样式无法方便地根据路由状态来进行样式控制。
说说你对React Router的理解常用的Router组件有哪些
react-router等前端路由的原理大致相同可以实现无刷新的条件下切换显示不同的页面
路由的本质就是页面的URL发生改变时页面的显示结果可以根据URL的变化而变化但是页面不会刷新
主要是提供了一些组件
BrowserRouter、HashRouterRouteLink、NavLinkswitchredirect
说说React Router有几种模式以及实现原理 Hash 模式 在 Hash 模式下URL 中会包含一个 # 符号例如 http://www.example.com/#/about。实现原理是基于浏览器对 URL 中哈希部分的变化不会引起页面刷新的特性。React Router 监听 URL 中哈希部分的变化并根据哈希值匹配相应的路由进行页面更新。这种模式兼容性好可以在不支持 HTML5 History API 的浏览器上正常工作但 URL 中包含 # 符号可能会被认为不够美观。 History 模式 在 History 模式下URL 不包含 # 符号例如 http://www.example.com/about。实现原理是利用 HTML5 History API 中的 pushState 和 replaceState 方法通过改变 URL 而不引起页面刷新来实现页面路由的切换。这种模式生成的 URL 更加美观但需要确保服务器端能够正确处理这些 URL以避免在用户直接访问这些 URL 时出现 404 错误。
使用 redux 有哪些原则
单一数据源状态是只读的纯函数的 reducer不要在 reducer 中执行异步操作合理使用中间件按照功能划分 reducer避免过度使用 Redux
Redux 和 Vuex 有什么区别它们有什么共同思想吗
们的共同思想包括 集中化的状态管理Redux 和 Vuex 都提倡将应用程序的状态集中管理以便于统一管理和跟踪状态的变化。 单向数据流两者都采用了单向数据流的思想即数据的流动是单向的便于状态的追踪和调试。 纯函数Redux 和 Vuex 都鼓励使用纯函数来处理状态的变化使得状态的变化更加可预测和可控。 开发者工具两者都提供了开发者工具便于开发者监控状态变化、调试和时间旅行等功能。
它们的区别主要在于以下几点 框架依赖Redux 是一个独立的状态管理库可以与任何框架结合使用而 Vuex 是专门为 Vue.js 设计的状态管理库。 概念和APIRedux 使用了 action、reducer、store 等概念和 API而 Vuex 使用了 mutation、action、state、getter 等不同的概念和 API。 语法差异Redux 的语法比较简洁但在某些情况下需要编写较多的模板代码而 Vuex 在 Vue.js 中能够更好地利用框架的特性提供了更加简洁的语法。 生态和社区Redux 有着庞大的生态和社区支持而 Vuex 则更加贴近 Vue.js 生态能够更好地与 Vue.js 整合。
React 中怎么实现状态自动保存KeepAlive 使用 Context API 或 Redux 可以使用 React 的 Context API 或 Redux 来全局管理组件的状态。将需要缓存的组件状态存储在全局状态管理中这样即使组件被卸载再重新挂载时状态仍然可以被保留。 使用组件缓存技术 可以自定义一个高阶组件HOC或 Hook用于缓存组件的状态。当组件被卸载时将状态保存到缓存中当组件重新挂载时从缓存中读取之前保存的状态。 使用 React Router 中的路由状态 如果是基于 React Router 实现页面导航的应用可以利用 React Router 提供的路由状态来实现状态自动保存。当路由切换时可以在路由状态中存储和恢复组件状态。 利用 LocalStorage 或 SessionStorage 可以使用浏览器提供的 LocalStorage 或 SessionStorage 来保存组件状态。在组件的生命周期方法中或者 useEffect 钩子中将状态保存到 LocalStorage 或 SessionStorage 中在组件重新挂载时再从中读取状态。 利用第三方库 也可以考虑使用像 react-keep-alive 这样的第三方库来实现状态自动保存的功能。这些库一般提供了简单易用的方式来实现组件状态的缓存和自动保存。
useEffect 与 useLayoutEffect 有什么区别 useEffect useEffect 是 React 提供的标准 Hook它会在浏览器渲染完成后异步执行副作用操作。这意味着它不会阻塞浏览器渲染并且会在浏览器布局和绘制更新之后执行。通常用于处理数据获取、订阅或手动操作 DOM 等副作用操作它不会阻塞浏览器的渲染过程。 useLayoutEffect useLayoutEffect 也是一个 React 提供的 Hook与 useEffect 类似但它会在所有 DOM 变更之后同步执行副作用操作但在浏览器布局和绘制之前执行。由于 useLayoutEffect 是在 DOM 更新之后、页面重新布局之前同步执行的因此如果在其中执行大量计算或操作可能会导致性能问题甚至造成页面卡顿。
因此主要区别在于执行时机和对页面布局的影响。一般情况下推荐优先使用 useEffect 来处理副作用操作只有在确实需要在布局更新之前立即执行代码时才考虑使用 useLayoutEffect。 react中懒加载的实现原理是什么 动态导入 懒加载的关键在于使用动态导入来按需加载组件或其他资源。通过使用 import() 函数可以在需要时动态加载模块而不是在应用初始化时一次性加载所有模块。例如可以将组件的引入语句改为 const LazyComponent React.lazy(() import(./LazyComponent))这样在需要时才会加载 LazyComponent 组件。 React.lazy 和 Suspense React 提供了 React.lazy 和 Suspense 这两个懒加载相关的 API。React.lazy 函数接受一个函数该函数应该返回一个动态 import 的 Promise以实现组件的懒加载。使用 Suspense 组件可以在等待懒加载组件加载完成时显示 loading 界面以提升用户体验。在最外层包裹 Suspense 组件并设置 fallback 属性为加载中时显示的组件即可实现懒加载时的 loading 效果。 代码分割 除了懒加载组件外还可以利用代码分割Code Splitting来拆分应用代码减少初始加载时的体积。通过将应用代码分割成多个块并在需要时动态加载这些块可以进一步优化页面加载性能。
React有哪些性能优化的方法
React 渲染性能优化的三个方向其实也适用于其他软件开发领域这三个方向分别是:
减少计算的量。 - 对应到 React 中就是减少渲染的节点 或者 降低组件渲染的复杂度利用缓存。- 对应到 React 中就是如何避免重新渲染利用函数式编程的 memo 方式来避免组件重新渲染精确重新计算的范围。 对应到 React 中就是绑定组件和状态关系, 精确判断更新的时机和范围. 只重新渲染脏的组件或者说降低渲染范围
简单介绍下React中的 diff 算法
在 React 中虚拟 DOM 的 diff 算法是用来比较前后两次 Virtual DOM 树的差异以确定最小的更新量从而高效地更新实际 DOM。React 采用了一种基于 Fiber 架构的调度算法其中的 diff 算法是其核心之一。
React 中的 diff 算法主要包括以下步骤 树的遍历 React 使用深度优先搜索算法DFS来遍历前后两棵 Virtual DOM 树对节点进行比较。 节点比较 对比同一层级的节点首先会比较节点类型是否相同然后再比较节点的属性。如果节点类型不同则直接替换整个节点及其子节点如果节点类型相同则比较节点属性更新需要更新的属性。 列表节点的处理 在处理列表时React 会使用“key”来唯一标识列表中的每一项以便更准确地进行节点的复用和更新。 差异的收集 在比较过程中React 会记录下需要进行更新、插入、移动或删除的操作形成一份变更列表。 批量更新 最后React 会根据变更列表中的操作批量更新实际 DOM以最小化页面重绘和重新排版的开销。
react 的虚拟dom是怎么实现的
通过 JavaScript 对象来模拟真实 DOM 的层次结构和状态从而实现了对 DOM 的抽象和操作。虚拟 DOM 的实现可以简单概括为以下几个步骤 创建虚拟 DOM 对象 当使用 React 创建组件时每个组件都会对应一个虚拟 DOM 对象。这个对象描述了组件在特定时间点的状态和结构。 渲染虚拟 DOM 当组件状态发生变化或初始化时React 会根据组件的 render 方法返回的 JSX 创建新的虚拟 DOM 对象。 对比更新 当新的虚拟 DOM 对象产生后React 会使用 diff 算法比较新旧虚拟 DOM 对象之间的差异并找出最小的更新量。 应用更新 经过对比后React 会将变更部分应用到实际的 DOM 结构上从而更新页面显示。
通过虚拟 DOMReact 实现了一种高效的页面更新机制。在组件状态发生变化时React 首先更新虚拟 DOM然后通过 diff 算法找出实际需要更新的部分最终只更新实际 DOM 中发生变化的部分避免了不必要的 DOM 操作提高了页面渲染的效率。
为什么不能直接使用 this.state 改变数据
setState通过一个队列机制来实现 state 更新。当执行 setState 的时候会将需要更新的 state 合并后放入状态队列而不会立刻更新 this.state。队列机制可以高效的批量更新 state如果不通过 setState 而直接修改 this.state那么该 state 将不会被放入状态队列中当下次调用 setState 并对状态队列进行合并时将会忽略之前被直接修改的 state而造成无法预知的错误。