reactjs-interview-questions:基于 React 技术的面试题资源项目

List of top 500 ReactJS Interview Questions & Answers....Coding exercise questions are coming soon!!

Branch4Tags0
This repository is empty

React 面试题及答案

如果你喜欢这个项目,请点击 ⭐。非常欢迎提交 Pull Requests。关注我 @SudheerJonna 获取技术更新。


在浏览器中练习 280 多个 JavaScript 编码面试题。由前 FAANG 面试官打造。无 AI 生成的冗余内容。无虚假评价。立即尝试 GreatFrontEnd → 💡

我推荐这门 React 课程,助你成为 React 领域的顶尖 10% 人才;还有这门 编码面试训练营,帮你在编码面试中脱颖而出并成功获得工作机会。



注意: 本仓库专门针对 ReactJS。核心 JavaScript 问题请查看 JavaScript Interview Questions,数据结构与算法相关问题请查看 Data Structures and Algorithms

目录

隐藏/显示目录
序号 问题
React 核心概念
1 什么是 React?
2 React 的发展历史是怎样的?
3 React 的主要特性有哪些?
4 什么是 JSX?
5 元素(Element)和组件(Component)有什么区别?
6 如何在 React 中创建组件?
7 什么时候应该使用类组件而不是函数组件?
8 什么是纯组件(Pure Components)?
9 React 中的 state 是什么?
10 React 中的 props 是什么?
11 state 和 props 有什么区别?
12 HTML 和 React 事件处理有什么区别?
13 React 中的合成事件(synthetic events)是什么?
14 什么是内联条件表达式?
15 什么是 "key" 属性?在元素数组中使用它有什么好处?
16 什么是虚拟 DOM(Virtual DOM)?
17 虚拟 DOM 是如何工作的?
18 Shadow DOM 和 Virtual DOM 有什么区别?
19 什么是 React Fiber?
20 React Fiber 的主要目标是什么?
21 什么是受控组件(Controlled Components)?
22 什么是非受控组件(Uncontrolled Components)?
23 createElement 和 cloneElement 有什么区别?
24 React 中的状态提升(Lifting State Up)是什么?
25 什么是高阶组件(Higher-Order Components)?
26 children prop 是什么?
27 如何在 React 中写注释?
28 什么是协调(reconciliation)?
29 lazy 函数支持命名导出吗?
30 为什么 React 使用 className 而不是 class 属性?
31 什么是 Fragments?
32 为什么 Fragments 比容器 div 更好?
33 React 中的 portals 是什么?
34 什么是无状态组件(Stateless Components)?
35 什么是有状态组件(Stateful Components)?
36 如何在 React 中对 props 进行验证?
37 React 的优点有哪些?
38 React 的局限性有哪些?
39 静态类型检查的推荐方式有哪些?
40 react-dom 包的作用是什么?
41 什么是 ReactDOMServer?
42 如何在 React 中使用 innerHTML?
43 如何在 React 中应用样式?
44 React 中的事件有何不同?
45 使用索引作为 key 有什么影响?
46 如何有条件地渲染组件?
47 为什么在 DOM 元素上展开 props 时需要小心?
48 如何记忆化(memoize)一个组件?
49 如何实现服务端渲染(SSR)?
50 如何在 React 中启用生产模式?
51 Hooks 是否会取代 render props 和高阶组件?
52 什么是切换组件(switching component)?
54 React 支持哪些指针事件?
55 为什么组件名称应该以大写字母开头?
56 React v16 是否支持自定义 DOM 属性?
57 如何在 JSX 中进行循环?
58 如何在属性引号中访问 props?
59 什么是 React PropType 数组与形状(shape)?
60 如何有条件地应用类属性?
61 React 和 ReactDOM 有什么区别?
62 为什么 ReactDOM 与 React 分离?
63 如何使用 React 的 label 元素?
64 如何合并多个内联样式对象?
65 浏览器窗口大小改变时如何重新渲染视图?
66 如何在 React 中格式化输出 JSON?
67 为什么不能在 React 中更新 props?
68 如何在页面加载时聚焦一个输入元素?
69 如何在浏览器运行时查看 React 的版本?
70 如何为 React Router 添加 Google Analytics?
71 如何在 React 的内联样式中应用浏览器前缀(vendor prefixes)?
72 如何使用 React 和 ES6 导入和导出组件?
73 React 组件命名有哪些例外情况?
74 在纯 React 中可以使用 async/await 吗?
75 React 常见的文件夹结构有哪些?
76 常用的动画包有哪些?
77 CSS Modules(样式模块)的好处是什么?
78 流行的 React 专用代码检查工具(linters)有哪些?
React Router
79 什么是 React Router?
80 React Router 与 history 库有什么区别?
81 React Router v6 的 <Router> 组件有哪些?
| 82 | [history 的 push 和 replace 方法的作用是什么?](#what-is-the-purpose-of-push-and-replace-methods-of-history) | | 83 | [如何使用 React Router v4 进行编程式导航?](#how-do-you-programmatically-navigate-using-react-router-v4) | | 84 | [在 React Router v4 中如何获取查询参数?](#how-to-get-query-parameters-in-react-router-v4) | | 85 | [为什么会收到“Router 只能有一个子元素”的警告?](#why-you-get-router-may-have-only-one-child-element-warning) | | 86 | [在 React Router v4 中如何向 history.push 方法传递参数?](#how-to-pass-params-to-historypush-method-in-react-router-v4) | | 87 | [如何实现默认页面或 NotFound 页面?](#how-to-implement-default-or-notfound-page) | | 88 | [在 React Router v4 中如何获取 history?](#how-to-get-history-on-react-router-v4) | | 89 | [如何在登录后执行自动重定向?](#how-to-perform-automatic-redirect-after-login) | | **React 国际化** | | 90 | [什么是 React Intl?](#what-is-react-intl) | | 91 | [React Intl 的主要特性是什么?](#what-are-the-main-features-of-react-intl) | | 92 | [React Intl 中有哪两种格式化方式?](#what-are-the-two-ways-of-formatting-in-react-intl) | | 93 | [如何将 FormattedMessage 用作 React Intl 中的占位符?](#how-to-use-formattedmessage-as-placeholder-using-react-intl) | | 94 | [如何使用 React Intl 获取当前区域设置?](#how-to-access-current-locale-with-react-intl) | | 95 | [如何使用 React Intl 格式化日期?](#how-to-format-date-using-react-intl) | | | **React 测试** | | 96 | [React 测试中的浅渲染器(Shallow Renderer)是什么?](#what-is-shallow-renderer-in-react-testing) | | 97 | [React 中的 TestRenderer 包是什么?](#what-is-testrenderer-package-in-react) | | 98 | [ReactTestUtils 包的作用是什么?](#what-is-the-purpose-of-reacttestutils-package) | | 99 | [什么是 Jest?](#what-is-jest) | | 100 | [Jest 相比 Jasmine 有哪些优势?](#what-are-the-advantages-of-jest-over-jasmine) | | 101 | [能否给出一个简单的 Jest 测试用例示例?](#give-a-simple-example-of-jest-test-case) | | | **React Redux** | | 102 | [什么是 Flux?](#what-is-flux) | | 103 | [什么是 Redux?](#what-is-redux) | | 104 | [Redux 的核心原则是什么?](#what-are-the-core-principles-of-redux) | | 105 | [与 Flux 相比,Redux 有哪些缺点?](#what-are-the-downsides-of-redux-compared-to-flux) | | 106 | [mapStateToProps() 和 mapDispatchToProps() 之间的区别是什么?](#what-is-the-difference-between-mapstatetoprops-and-mapdispatchtoprops) | | 107 | [能否在 reducer 中派发 action?](#can-i-dispatch-an-action-in-reducer) | | 108 | [如何在组件外部访问 Redux store?](#how-to-access-redux-store-outside-a-component) | | 109 | [MVW 模式的缺点是什么?](#what-are-the-drawbacks-of-mvw-pattern) | | 110 | [Redux 和 RxJS 之间有什么相似之处吗?](#are-there-any-similarities-between-redux-and-rxjs) | | 111 | [如何在 Redux 中重置状态?](#how-to-reset-state-in-redux) | | 112 | [React Context 和 React Redux 之间的区别是什么?](#what-is-the-difference-between-react-context-and-react-redux) | | 113 | [为什么 Redux 中的状态函数被称为 reducer?](#why-are-redux-state-functions-called-reducers) | | 114 | [如何在 Redux 中发起 AJAX 请求?](#how-to-make-ajax-request-in-redux) | | 115 | [是否应该将所有组件状态都保存在 Redux store 中?](#should-i-keep-all-components-state-in-redux-store) | | 116 | [访问 Redux store 的正确方式是什么?](#what-is-the-proper-way-to-access-redux-store) | | 117 | [React Redux 中的组件(component)和容器(container)有什么区别?](#what-is-the-difference-between-component-and-container-in-react-redux) | | 118 | [Redux 中常量的作用是什么?](#what-is-the-purpose-of-the-constants-in-redux) | | 119 | [编写 mapDispatchToProps() 的不同方式有哪些?](#what-are-the-different-ways-to-write-mapdispatchtoprops) | | 120 | [mapStateToProps() 和 mapDispatchToProps() 中的 ownProps 参数有什么用途?](#what-is-the-use-of-the-ownprops-parameter-in-mapstatetoprops-and-mapdispatchtoprops) | | 121 | [如何组织 Redux 的顶级目录结构?](#how-to-structure-redux-top-level-directories) | | 122 | [什么是 Redux Saga?](#what-is-redux-saga) | | 123 | [Redux Saga 的思维模型是什么?](#what-is-the-mental-model-of-redux-saga) | | 124 | [Redux Saga 中的 call 和 put 有什么区别?](#what-are-the-differences-between-call-and-put-in-redux-saga) | | 125 | [什么是 Redux Thunk?](#what-is-redux-thunk) | | 126 | [Redux Saga 和 Redux Thunk 之间有什么区别?](#what-are-the-differences-between-redux-saga-and-redux-thunk) | | 127 | [什么是 Redux DevTools?](#what-is-redux-devtools) | | 128 | [Redux DevTools 有哪些功能?](#what-are-the-features-of-redux-devtools) | | 129 | [什么是 Redux 选择器(selectors)?为什么要使用它们?](#what-are-redux-selectors-and-why-use-them) | | 130 | [什么是 Redux Form?](#what-is-redux-form) | | 131 | [Redux Form 的主要特性是什么?](#what-are-the-main-features-of-redux-form) | | 132 | [如何向 Redux 添加多个中间件?](#how-to-add-multiple-middlewares-to-redux) | | 133 | [如何在 Redux 中设置初始状态?](#how-to-set-initial-state-in-redux) | | 134 | [Relay 与 Redux 有何不同?](#how-relay-is-different-from-redux) | | 135 | [Redux 中的 action 是什么?](#what-is-an-action-in-redux) | | | **React Native** | | 136 | [React Native 和 React 之间的区别是什么?](#what-is-the-difference-between-react-native-and-react) | | 137 | [如何测试 React Native 应用?](#how-to-test-react-native-apps) | | 138 | [在 React Native 中如何进行日志记录?](#how-to-do-logging-in-react-native) | | 139 | [如何调试 React Native 应用?](#how-to-debug-your-react-native) | | | **React 支持的库和集成** | | 140 | [什么是 Reselect?它是如何工作的?](#what-is-reselect-and-how-it-works) | | 141 | [什么是 Flow?](#what-is-flow) | | 142 | [Flow 和 PropTypes 之间的区别是什么?](#what-is-the-difference-between-flow-and-proptypes) | | 143 | [如何在 React 中使用 Font Awesome 图标?](#how-to-use-font-awesome-icons-in-react) | | 144 | [什么是 React DevTools?](#what-is-react-dev-tools) | | 145 | [为什么 Chrome 中 DevTools 无法加载本地文件?](#why-is-devtools-not-loading-in-chrome-for-local-files) | | 146 | [如何在 React 中使用 Polymer?](#how-to-use-polymer-in-react) | | 147 | [React 相比 Vue.js 有哪些优势?](#what-are-the-advantages-of-react-over-vuejs) | | 148 | [React 和 Angular 之间的区别是什么?](#what-is-the-difference-between-react-and-angular) | | 149 | [为什么 DevTools 中的 React 选项卡不显示?](#why-react-tab-is-not-showing-up-in-devtools) | | 150 | [什么是 styled-components?](#what-are-styled-components) | | 151 | [能否给出 styled-components 的示例?](#give-an-example-of-styled-components) | | 152 | [什么是 Relay?](#what-is-relay) | | | **其他** | | 153 | [Reselect 库的主要特性是什么?](#what-are-the-main-features-of-reselect-library) | | 154 | [能否给出 Reselect 的使用示例?](#give-an-example-of-reselect-usage) | | 155 | [Redux 是否只能与 React 一起使用?](#can-redux-only-be-used-with-react) | | 156 | [使用 Redux 是否需要特定的构建工具?](#do-you-need-to-have-a-particular-build-tool-to-use-redux) | | 157 | [Redux Form 的 initialValues 如何从状态中更新?](#how-redux-form-initialvalues-get-updated-from-state) | | 158 | [React PropTypes 如何允许一个 prop 接受不同类型?](#how-react-proptypes-allow-different-types-for-one-prop) | | 159 | [能否将 SVG 文件作为 React 组件导入?](#can-i-import-an-svg-file-as-react-component) | | 160 | [React 中的渲染劫持(render hijacking)是什么?](#what-is-render-hijacking-in-react) | | 161 | [如何向 React 组件传递数字?](#how-to-pass-numbers-to-react-component) | | 162 | [是否需要将所有状态都保存在 Redux 中?是否应该使用 React 的内部状态?](#do-i-need-to-keep-all-my-state-into-redux-should-i-ever-use-react-internal-state) | | 163 | [React 中 registerServiceWorker 的作用是什么?](#what-is-the-purpose-of-registerserviceworker-in-react) | | 164 | [React.memo 函数是什么?](#what-is-react-memo-function) | | 165 | [React.lazy 函数是什么?](#what-is-react-lazy-function) | | 166 | [如何使用 setState 防止不必要的更新?](#how-to-prevent-unnecessary-updates-using-setstate) | | 167 | [在 React v16 中如何渲染数组、字符串和数字?](#how-do-you-render-array-strings-and-numbers-in-react-16-version) | | 168 | [什么是 Hooks?](#what-are-hooks) | | 169 | [使用 Hooks 必须遵循哪些规则?](#what-rules-need-to-be-followed-for-hooks) | | 170 | [如何确保项目中的 Hooks 遵循规则?](#how-to-ensure-hooks-followed-the-rules-in-your-project) | | 171 | [Flux 和 Redux 有什么区别?](#what-are-the-differences-between-flux-and-redux) | | 172 | [React Router v4 有哪些优点?](#what-are-the-benefits-of-react-router-v4) | | 173 | [你能描述一下 componentDidCatch 生命周期方法的签名吗?](#can-you-describe-about-componentdidcatch-lifecycle-method-signature) | | 174 | [在哪些场景下错误边界无法捕获错误?](#in-which-scenarios-do-error-boundaries-not-catch-errors) | | 175 | [在 React v16 中未捕获错误的行为是什么?](#what-is-the-behavior-of-uncaught-errors-in-react-16) | | 176 | [错误边界的正确放置位置在哪里?](#what-is-the-proper-placement-for-error-boundaries) | | 177 | [错误边界的组件堆栈跟踪有什么好处?](#what-is-the-benefit-of-component-stack-trace-from-error-boundary) | | 178 | [什么是默认 props?](#what-are-default-props) | | 179 | [displayName 类属性的作用是什么?](#what-is-the-purpose-of-displayname-class-property) | | 180 | [React 应用程序的浏览器支持情况如何?](#what-is-the-browser-support-for-react-applications) | | 181 | [什么是代码分割?](#what-is-code-splitting) | | 182 | [什么是带键的 Fragments?](#what-are-keyed-fragments) | | 183 | [React 是否支持所有 HTML 属性?](#does-react-support-all-html-attributes) | | 184 | [组件 props 在什么情况下默认为 true?](#when-component-props-defaults-to-true) | | 185 | [什么是 Next.js 及其主要特性是什么?](#what-is-nextjs-and-major-features-of-it) | | 186 | [如何向组件传递事件处理程序?](#how-do-you-pass-an-event-handler-to-a-component) | | 187 | [如何防止函数被多次调用?](#how-to-prevent-a-function-from-being-called-multiple-times) | | 188 | [JSX 如何防止注入攻击?](#how-jsx-prevents-injection-attacks) | | 189 | [如何更新已渲染的元素?](#how-do-you-update-rendered-elements) | | 190 | [如何表明 props 是只读的?](#how-do-you-say-that-props-are-read-only) | | 191 | [安全使用索引作为 key 的条件是什么?](#what-are-the-conditions-to-safely-use-the-index-as-a-key) | | 192 | [keys 是否需要全局唯一?](#is-it-keys-should-be-globally-unique) | | 193 | [表单处理的常用选择是什么?](#what-is-the-popular-choice-for-form-handling) | | 194 | [Formik 相比 Redux Form 库有哪些优势?](#what-are-the-advantages-of-formik-over-redux-form-library) | | 195 | [为什么不需要使用继承?](#why-do-you-not-required-to-use-inheritance) | | 196 | [可以在 React 应用程序中使用 Web Components 吗?](#can-i-use-web-components-in-react-application) | | 197 | [什么是动态导入?](#what-is-dynamic-import) | | 198 | [什么是可加载组件?](#what-are-loadable-components) | | 199 | [什么是 Suspense 组件?](#what-is-suspense-component) | | 200 | [什么是基于路由的代码分割?](#what-is-route-based-code-splitting) | | 201 | [Context 中默认值的作用是什么?](#what-is-the-purpose-of-default-value-in-context) | | 202 | [什么是差异算法?](#what-is-diffing-algorithm) | | 203 | [差异算法涵盖哪些规则?](#what-are-the-rules-covered-by-diffing-algorithm) | | 204 | [什么时候需要使用 refs?](#when-do-you-need-to-use-refs) | | 205 | [render props 中的 prop 必须命名为 "render" 吗?](#is-it-prop-must-be-named-as-render-for-render-props) | | 206 | [将 render props 与 Pure Components 一起使用有什么问题?](#what-are-the-problems-of-using-render-props-with-pure-components) | | 207 | [什么是窗口化技术?](#what-is-windowing-technique) | | 208 | [如何在 JSX 中打印假值?](#how-do-you-print-falsy-values-in-jsx) | | 209 | [Portals 的典型用例是什么?](#what-is-the-typical-use-case-of-portals) | | 210 | [如何为非受控组件设置默认值?](#how-do-you-set-default-value-for-uncontrolled-component) | | 211 | [你最喜欢的 React 技术栈是什么?](#what-is-your-favorite-react-stack) | | 212 | [真实 DOM 和虚拟 DOM 有什么区别?](#what-is-the-difference-between-real-dom-and-virtual-dom) | | 213 | [如何向 React 应用程序添加 Bootstrap?](#how-to-add-bootstrap-to-a-react-application) | | 214 | [你能列出使用 React 作为前端框架的顶级网站或应用程序吗?](#can-you-list-down-top-websites-or-applications-using-react-as-front-end-framework) | | 215 | [在 React 中推荐使用 CSS-in-JS 技术吗?](#is-it-recommended-to-use-css-in-js-technique-in-react) | | 216 | [是否需要用 Hooks 重写所有类组件?](#do-i-need-to-rewrite-all-my-class-components-with-hooks) | | 217 | [如何使用 React Hooks 获取数据?](#how-to-fetch-data-with-react-hooks) | | 218 | [Hooks 是否涵盖类的所有使用场景?](#is-hooks-cover-all-use-cases-for-classes) | | 219 | [支持 Hooks 的稳定版本是什么?](#what-is-the-stable-release-for-hooks-support) | | 220 | [为什么在 useState 中使用数组解构(方括号表示法)?](#why-do-we-use-array-destructuring-square-brackets-notation-in-usestate) | | 221 | [引入 Hooks 的依据是什么?](#what-are-the-sources-used-for-introducing-hooks) | | 222 | [如何访问 Web Components 的命令式 API?](#how-do-you-access-imperative-api-of-web-components) | | 223 | [什么是 Formik?](#what-is-formik) | | 224 | [在 Redux 中处理异步调用的典型中间件选择有哪些?](#what-are-typical-middleware-choices-for-handling-asynchronous-calls-in-redux) | | 225 | [浏览器能理解 JSX 代码吗?](#do-browsers-understand-jsx-code) | | 226 | [你能描述一下 React 中的数据流向吗?](#describe-about-data-flow-in-react) | | 227 | [什么是 MobX?](#what-is-mobx) | | 228 | [Redux 和 MobX 有什么区别?](#what-are-the-differences-between-redux-and-mobx) | | 229 | [学习 ReactJS 之前应该学习 ES6 吗?](#should-i-learn-es6-before-learning-reactjs) | | 230 | [什么是并发渲染?](#what-is-concurrent-rendering) | | 231 | [异步模式和并发模式有什么区别?](#what-is-the-difference-between-async-mode-and-concurrent-mode) | | 233 | [Hooks 的 ESLint 插件的作用是什么?](#what-is-the-purpose-of-eslint-plugin-for-hooks) | | 234 | [React 中的命令式编程和声明式编程有什么区别?](#what-is-the-difference-between-imperative-and-declarative-in-react) | | 235 | [在 ReactJS 中使用 TypeScript 有哪些好处?](#what-are-the-benefits-of-using-typescript-with-reactjs) | | 236 | [在使用 Context API 进行状态管理时,如何确保用户在页面刷新后仍然保持登录状态?](#how-do-you-make-sure-that-user-remains-authenticated-on-page-refresh-while-using-context-api-state-management) | | 237 | [新的 JSX 转换有哪些好处?](#what-are-the-benefits-of-new-jsx-transform) | | 238 | [新的 JSX 转换与旧转换有何不同?](#how-is-the-new-jsx-transform-different-from-old-transform) | | 239 | [什么是 React Server Components?](#what-are-react-server-components) | | 240 | [什么是属性传递链(prop drilling)?](#what-is-prop-drilling) | | 241 | [useState 和 useRef Hooks 有什么区别?](#what-is-the-difference-between-usestate-and-useref-hook) | | 242 | [什么是包装组件(wrapper component)?](#what-is-a-wrapper-component) | | 243 | [useEffect 和 useLayoutEffect Hooks 有什么区别?](#what-are-the-differences-between-useeffect-and-uselayouteffect-hooks) | | 244 | [函数组件和类组件有什么区别?](#what-are-the-differences-between-functional-and-class-components) | | 245 | [React 中的严格模式(Strict Mode)是什么?](#what-is-strict-mode-in-react) | | 246 | [严格模式的好处是什么?](#what-is-the-benefit-of-strict-mode) | | 247 | [为什么 React 中的严格模式会渲染两次?](#why-does-strict-mode-render-twice-in-react) | | 248 | [JSX 的规则有哪些?](#what-are-the-rules-of-jsx) | | 249 | [多个 JSX 标签必须被包裹的原因是什么?](#what-is-the-reason-behind-multiple-jsx-tags-to-be-wrapped) | | 250 | [如何防止数组变量被修改?](#how-do-you-prevent-mutating-array-variables) | | 251 | [什么是捕获阶段事件?](#what-are-capture-phase-events) | | 252 | [React 如何更新应用程序中的屏幕?](#how-does-react-updates-screen-in-an-application) | | 253 | [React 如何批处理多个状态更新?](#how-does-react-batch-multiple-state-updates) | | 254 | [是否可以阻止自动批处理?](#is-it-possible-to-prevent-automatic-batching) | | 255 | [什么是 React hydration?](#what-is-react-hydration) | | 256 | [如何更新状态中的对象?](#how-do-you-update-objects-inside-state) | | 257 | [如何更新状态中的嵌套对象?](#How-do-you-update-nested-objects-inside-state) | | 258 | [如何更新状态中的数组?](#how-do-you-update-arrays-inside-state) | | 259 | [如何使用 Immer 库进行状态更新?](#how-do-you-use-immer-library-for-state-updates) | | 260 | [防止直接修改状态有哪些好处?](#what-are-the-benefits-of-preventing-the-direct-state-mutations) | | 261 | [更新状态时首选和非首选的数组操作有哪些?](#what-are-the-preferred-and-non-preferred-array-operations-for-updating-the-state) | | 262 | [定义嵌套函数组件会发生什么?](#what-will-happen-by-defining-nested-function-components) | | 263 | [可以为非列表项使用 keys 吗?](#can-i-use-keys-for-non-list-items) | | 264 | [编写 reducers 应遵循哪些准则?](#what-are-the-guidelines-to-be-followed-for-writing-reducers) | | | **Hooks** | | 265 | [什么是 useReducer hook?你能描述它的用法吗?](#what-is-use-reducer-hook-can-you-describe-its-usage) | | 266 | [如何比较 useState 和 useReducer?](#how-do-you-compare-use-state-and-use-reducer) | | 267 | [Context 如何与 useContext hook 配合使用?](#how-does-context-works-using-usecontext-hook) | | 268 | [useContext hook 的用例有哪些?](#what-are-the-use-cases-of-usecontext-hook) | | 269 | [何时应该使用客户端组件和服务器组件?](#when-to-use-client-and-server-components) | | 270 | [Next.js 中的 Page Router 和 App Router 有什么区别?](#what-are-the-differences-between-page-router-and-app-router-in-nextjs) | | 271 | [你能描述一下 useMemo() Hook 吗?](#can-you-describe-the-usememo-hook) | | 272 | [Hooks 可以在类组件中使用吗?](#can-hooks-be-used-in-class-components) | | 273 | [什么是更新函数?是否应该在所有情况下都使用更新函数?](#what-is-an-updater-function-should-an-updater-function-be-used-in-all-cases) | | 274 | [useState 可以接受函数作为初始值吗?](#can-usestate-take-a-function-as-an-initial-value) | | 275 | [useState 可以保存哪些类型的值?](#what-types-of-values-can-usestate-hold) | | 276 | [如果有条件地调用 useState 会发生什么?](#what-happens-if-you-call-usestate-conditionally) | | 277 | [useState 是同步的还是异步的?](#is-usestate-synchronous-or-asynchronous) | | 278 | [你能解释 useState 内部是如何工作的吗?](#can-you-explain-how-usestate-works-internally) | | 279 | [什么是 useReducer?为什么要使用 useReducer?](#what-is-usereducer-why-do-you-use-usereducer) | | 280 | [useReducer 是如何工作的?举例说明](#how-does-usereducer-works-explain-with-an-example) | | 281 | [可以将 useReducer 与 useContext 结合使用吗?](#can-you-combine-usereducer-with-usecontext) | | 282 | [可以使用 useReducer 连续调度多个 action 吗?](#can-you-dispatch-multiple-actions-in-a-row-with-usereducer) | | 283 | useReducer 的 dispatch 是异步的吗?它会立即更新状态吗?](#is-dispatch-from-usereducer-asynchronous-and-does-it-update-state-immediately) | | 284 | [useContext 是如何工作的?举例说明](#how-does-usecontext-works-explain-with-an-example) | | 285 | [一个组件中可以使用多个 Context 吗?](#can-you-use-multiple-contexts-in-one-component) | | 286 | [使用 useContext 处理对象时常见的陷阱是什么?](#whats-a-common-pitfall-when-using-usecontext-with-objects) | | 287 | [没有匹配的 provider 时,context 的值是什么?](#what-would-the-context-value-be-for-no-matching-provider) | | 288 | [useEffect 依赖数组中的响应式依赖如何影响其执行行为?](#how-do-reactive-dependencies-in-the-useeffect-dependency-array-affect-its-execution-behavior) | | 289 | [React 何时以及多久调用一次 useEffect hook 内部的设置和清理函数?](#when-and-how-often-does-react-invoke-the-setup-and-cleanup-functions-inside-a-useeffect-hook) | | 290 | [如果从 useEffect 返回一个 Promise 会发生什么?](#what-happens-if-you-return-a-promise-from-useeffect) | | 291 | [一个组件中可以有多个 useEffect hook 吗?](#can-you-have-multiple-useeffect-hooks-in-a-single-component) | | 292 | [如何防止 useEffect 出现无限循环?](#how-to-prevent-infinite-loops-with-useeffect) | | 293 | [useLayoutEffect 的用例有哪些?](#what-are-the-usecases-of-uselayouteffect) | | 294 | [useLayoutEffect 在服务器端渲染 (SSR) 期间如何工作?](#how-does-uselayouteffect-work-during-server-side-rendering-ssr) | | 295 | [如果将 useLayoutEffect 用于非布局逻辑会发生什么?](#what-happens-if-you-use-uselayouteffect-for-non-layout-logic) | | 296 | [useLayoutEffect 如何导致布局抖动?](#how-does-uselayouteffect-cause-layout-thrashing) | | 297 | [如何在 React 中使用 useRef 访问 DOM 元素?举例说明](#how-do-you-use-useref-to-access-a-dom-element-in-react-give-an-example) | | 298 | [可以使用 useRef 在渲染之间持久化值吗?](#can-you-use-useref-to-persist-values-across-renders) | | 299 | [可以使用 useRef 存储先前的值吗?](#can-useref-be-used-to-store-previous-values) | | 300 | [是否可以在 render 方法中访问 ref?](#is-it-possible-to-access-a-ref-in-the-render-method) | | 301 | [useRef hook 的常见用例有哪些?](#what-are-the-common-usecases-of-useref-hook) | | 302 | [什么是 useImperativeHandle Hook?举例说明](#what-is-useimperativehandle-hook-give-an-example) | | 303 | [何时应该使用 useImperativeHandle?](#when-should-you-use-useimperativehandle) | | 304 | [没有 forwardRef 可以使用 useImperativeHandle 吗?](#is-that-possible-to-use-useimperativehandle-without-forwardref) | | 305 | [useMemo 和 useCallback 有什么区别?](#how-is-usememo-different-from-usecallback) | | 306 | [useMemo 可以防止子组件重新渲染吗?](#does-usememo-prevent-re-rendering-of-child-components) | | 307 | [什么是 useCallback?为什么使用它?](#what-is-usecallback-and-why-is-it-used) | | 308 | [什么是自定义 React Hooks?如何开发一个?](#what-are-custom-react-hooks-and-how-can-you-develop-one) | | 309 | [React Fiber 是如何工作的?详细解释](#how-does-react-fiber-works-explain-in-detail) | | 310 | [什么是 useId hook?何时应该使用它?](#what-is-the-useid-hook-and-when-should-you-use-it) | | 311 | [什么是 useDeferredValue hook?](#what-is-the-usedeferredvalue-hook) | | 312 | [什么是 useTransition hook?它与 useDeferredValue 有何不同?](#what-is-the-usetransition-hook-and-how-does-it-differ-from-usedeferredvalue) | | 313 | [什么是 useSyncExternalStore hook?](#what-is-the-usesyncexternalstore-hook) | | 314 | [什么是 useInsertionEffect hook?](#what-is-the-useinsertioneffect-hook) | | 315 | [如何使用自定义 hooks 在组件之间共享状态逻辑?](#how-do-you-share-state-logic-between-components-using-custom-hooks) | | 316 | [什么是 useDebugValue hook?](#what-is-the-usedebugvalue-hook) | | 317 | [如何在 useEffect 中处理清理工作?](#how-do-you-handle-cleanup-in-useeffect) | | 318 | [useEffect 和 useEvent(实验性)有什么区别?](#what-are-the-differences-between-useeffect-and-useevent-experimental) | | 319 | [使用 React Hooks 的最佳实践有哪些?](#what-are-the-best-practices-for-using-react-hooks) | | | **现代 React 特性(React 18/19)** | | 320 | [React 18 中引入的关键特性有哪些?](#what-are-the-key-features-introduced-in-react-18) | | 321 | [React 19 中引入的关键特性有哪些?](#what-are-the-key-features-introduced-in-react-19) | | 322 | [React 19 中的 use() hook 是什么?](#what-is-the-use-hook-in-react-19) | | 323 | [React 19 中的 Server Actions 是什么?](#what-are-server-actions-in-react-19) | | 324 | [什么是 useFormState 和 useFormStatus hooks?](#what-are-useformstate-and-useformstatus-hooks) | | 325 | [什么是 useOptimistic hook?](#what-is-the-useoptimistic-hook) | | 326 | [什么是 React Compiler(React Forget)?](#what-is-the-react-compiler-react-forget) | | 327 | [什么是 Streaming SSR?React 18+ 如何改进它?](#what-is-streaming-ssr-and-how-does-react-18-improve-it) |

目录

隐藏/显示目录
序号 问题
旧版问答
1 为什么不应该直接更新 state?
2 setState() 的参数中回调函数的作用是什么?
3 如何在 JSX 回调中绑定方法或事件处理程序?
4 如何向事件处理程序或回调传递参数?
5 refs 的作用是什么?
6 如何创建 refs?
7 什么是 forward refs?
8 在回调 refs 和 findDOMNode() 中,首选哪个选项?
9 为什么字符串 Refs 是遗留用法?
10 组件生命周期有哪些不同阶段?
11 React 的生命周期方法有哪些?
12 如何为 HOC 组件创建 props 代理?
13 什么是 context?
14 带 props 参数的 super 构造函数的作用是什么?
15 如何使用动态键名设置 state?
16 函数在组件每次渲染时都被调用,这通常是什么错误导致的?
17 React v16 中的错误边界是什么
18 React v15 中如何处理错误边界?
19 react-dom 的 render 方法的作用是什么?
20 如果在 constructor 中使用 setState 会发生什么?
21 在 componentWillMount() 方法中使用 setState() 好吗?
22 如果在初始 state 中使用 props 会发生什么?
23 如何在 React 中使用装饰器?
24 什么是 CRA 及其优点?
25 挂载阶段的生命周期方法顺序是什么?
26 React v16 中哪些生命周期方法将要被弃用?
27 getDerivedStateFromProps() 生命周期方法的作用是什么?
28 getSnapshotBeforeUpdate() 生命周期方法的作用是什么?
29 推荐的组件命名方式是什么?
30 组件类中方法的推荐排序是什么?
31 为什么需要向 setState() 传递一个函数?
32 为什么 isMounted() 是反模式,正确的解决方案是什么?
33 constructor 和 getInitialState 的区别是什么?
34 不调用 setState 能否强制组件重新渲染?
35 在使用 ES6 类的 React 中,super() 和 super(props) 的区别是什么?
36 setState 和 replaceState 方法的区别是什么?
37 如何监听 state 变化?
38 在 React state 中删除数组元素的推荐方法是什么?
39 是否可以在不渲染 HTML 的情况下使用 React?
40 更新 state 中对象的可能方法有哪些?
41 在 create-react-app 中包含 polyfills 的方法有哪些?
42 如何在 create-react-app 中使用 https 而不是 http?
43 如何在 create-react-app 中避免使用相对路径导入?
44 如何每秒更新一次组件?
45 为什么组件构造函数只被调用一次?
46 如何在 React 中定义常量?
47 如何在 React 中以编程方式触发点击事件?
48 如何进行 AJAX 调用,应该在哪个组件生命周期方法中进行 AJAX 调用?
49 什么是 render props?
50 如何在加载时调度一个 action?
51 如何使用 React Redux 中的 connect?
52 redux connect 装饰器中的 at 符号(@)有什么作用?
53 如何在 create-react-app 应用中使用 TypeScript?
54 statics 对象在 React 的 ES6 类中是否有效?
55 为什么不推荐使用内联 ref 回调或函数?
56 什么是 HOC 工厂实现?
57 如何在 React 类中使用类字段声明语法?
58 为什么事件处理程序不需要错误边界?
59 try catch 块和错误边界的区别是什么?
60 类组件必须定义的方法是什么?
61 render 方法可能的返回类型有哪些?
62 构造函数的主要作用是什么?
63 React 组件必须定义构造函数吗?
64 为什么不应该在 componentWillUnmount 中调用 setState?
65 getDerivedStateFromError 的作用是什么?
66 组件重新渲染时的方法调用顺序是什么?
67 错误处理期间会调用哪些方法?
68 unmountComponentAtNode 方法的作用是什么?
69 HOC 有哪些局限性?
70 如何在 DevTools 中调试 forwardRefs?
71 在 render 方法中使用箭头函数好吗?
72 如何理解 state 更新是合并的?
73 如何向事件处理程序传递参数?
74 如何阻止组件渲染?
75 举例说明如何使用 context?
76 如何使用 contextType?
77 什么是 consumer?
78 使用 context 时如何解决性能问题?
79 HOC 中 forward ref 的作用是什么?
80 ref 参数是否适用于所有函数或类组件?
81 为什么在使用 forward refs 时,组件库需要额外注意?
82 如何不使用 ES6 创建 React 类组件?
83 是否可以不使用 JSX 来使用 React?
84 如何使用 render props 创建 HOC?
85 什么是 react scripts?
86 create react app 有哪些功能?
87 renderToNodeStream 方法的作用是什么?
88 如何使用 create-react-app 获取 redux 脚手架?
89 什么是 state 突变,如何防止?
90 什么是 React Mixins?
91 在 react16.9 中可以使用 javascript urls 吗?

核心 React

  1. 什么是 React?

    React(又称 React.js 或 ReactJS)是一个开源的前端 JavaScript 库,用于基于组件构建用户界面。它用于处理 Web 和移动应用程序的视图层,允许开发人员创建可重用的 UI 组件并高效管理这些组件的状态。

    React 由 Facebook(现为 Meta)的软件工程师 Jordan Walke 创建。它于 2011 年首次部署在 Facebook 的动态消息上,2012 年部署在 Instagram 上。该库于 2013 年 5 月开源,此后成为构建现代用户界面最受欢迎的 JavaScript 库之一。

    ⬆ 返回顶部

  2. React 的发展历史是怎样的?

    ReactJS 的历史始于 2010 年 XHP 的创建。XHP 是一个 PHP 扩展,它改进了该语言的语法,使 XML 文档片段成为有效的 PHP 表达式,其主要目的是用于创建自定义和可重用的 HTML 元素。

    这个扩展的主要原则是使前端代码更易于理解,并帮助避免跨站脚本攻击。该项目通过清理用户提交的恶意内容成功地起到了防范作用。

    但是 XHP 存在一个不同的问题,即动态 Web 应用程序需要多次往返服务器,而 XHP 并没有解决这个问题。此外,应用程序中即使有很小的变化,整个 UI 也会重新渲染。后来,Jordan 受 XHP 的启发,创建了 React 的最初原型,名为 FaxJ。最终,经过一段时间后,React 作为一个新的库被引入到 JavaScript 世界中。

    查看深入解答 React 的演变有着超过十年的迷人历史:

    2010-2011 年:起源

    • 旅程始于 XHP,这是 Facebook 开发的一个 PHP 扩展,允许在 PHP 代码中使用 HTML 组件
    • XHP 提高了前端代码的可读性,并有助于防止跨站脚本(XSS)攻击
    • 然而,XHP 在动态 Web 应用程序方面存在局限性,需要频繁的服务器往返,并且对于小的更改也需要完全重新渲染 UI

    2011-2012 年:早期开发

    • Jordan Walke 创建了第一个原型,名为 FaxJS(后来更名为 React),其灵感来自 XHP 的组件模型
    • 关键创新在于将 XHP 的组件模型引入 JavaScript,并进行了性能改进
    • React 引入了虚拟 DOM 概念,以解决整页重新渲染的性能问题
    • 2011 年首次在 Facebook 的动态消息内部部署,2012 年在 Instagram 部署

    2013 年:公开发布

    • React 于 2013 年 5 月在 JSConf US 上正式开源
    • 最初的公众反响褒贬不一,一些开发人员对 JSX 语法以及将标记与 JavaScript 混合的方法持怀疑态度

    2014-2015 年:日益普及

    • React Native 于 2015 年宣布,将 React 的范式扩展到移动应用开发
    • 生态系统开始随着 Redux 等状态管理工具的出现而发展
    • Facebook 之外的公司开始在生产应用中采用 React

    2016-2018 年:成熟

    • React 16(“Fiber”)于 2017 年发布,对核心架构进行了彻底重写
    • 引入了错误边界、 portals 和改进的服务器端渲染等新功能
    • React 16.3 引入了 Context API,用于更轻松的状态管理

    2019 年至今:现代 React

    • React Hooks 于 React 16.8(2019 年 2 月)中引入,彻底改变了函数组件中的状态管理
    • React 17(2020 年 10 月)专注于使 React 升级更容易
    • React 18(2022 年 3 月)引入了并发渲染和自动批处理
    • React 继续通过服务器组件、新的 React 编译器(React Forget)和其他性能改进不断发展

    注意: React 的语法扩展 JSX 确实受到了 XHP 在代码中嵌入类 XML 语法方法的启发。

    ⬆ 返回顶部

  3. React 的主要特性是什么?

    React 提供了一系列强大的特性,使其成为构建用户界面最受欢迎的 JavaScript 库之一:

    核心特性:

    • 基于组件的架构:React 应用程序使用组件构建——组件是独立、可重用的代码片段,通过 render 函数返回 HTML。这种模块化方法使代码组织、可重用性和维护性更好。

    • 虚拟 DOM:React 创建内存中的数据结构缓存,计算结果差异,并高效地仅更新浏览器 DOM 中已更改的部分。与直接 DOM 操作相比,这种方法显著提高了性能。

    • JSX(JavaScript XML):一种语法扩展,允许在 JavaScript 中编写类 HTML 代码。JSX 使代码更具可读性和表达性,同时提供 JavaScript 的全部功能。

    • 单向数据流:React 遵循单向数据绑定模型,数据从父组件流向子组件。这使代码更可预测且更易于调试。

    • 声明式 UI:React 允许你描述给定状态下 UI 应该是什么样子,当底层数据变化时,它会处理 DOM 更新。

    高级特性:

    • React Hooks:在 React 16.8 中引入,Hooks 允许在函数组件中使用状态和其他 React 功能,而无需编写类。

    • Context API:提供了一种在组件之间共享值的方式,无需通过组件树的每个级别显式传递 props。

    • 错误边界:能够捕获其子组件树中任何位置的 JavaScript 错误,并显示备用 UI,而不是崩溃。

    • 服务器端渲染(SSR):允许在服务器上渲染 React 组件,然后再将 HTML 发送到客户端,从而提高性能和 SEO。

    • 并发模式:一组新功能(开发中),帮助 React 应用保持响应性,并优雅地适应用户的设备能力和网络速度。

    • React 服务器组件:一项新功能,允许组件完全在服务器上渲染,减少捆绑包大小并提高性能。

    • Suspense:允许组件在渲染前“等待”某些内容的功能,支持代码分割和更清晰的数据获取代码。

    这些特性共同使 React 强大到足以构建从小部件到复杂、大规模 Web 应用程序的各种项目。

    ⬆ 返回顶部

  4. 什么是 JSX?

    JSX 代表 JavaScript XML,它是 ECMAScript 的一种类 XML 语法扩展。基本上,它只是为 React.createElement(type, props, ...children) 函数提供语法糖,为我们提供 JavaScript 的表达能力以及类 HTML 的模板语法。

    在下面的示例中,<h1> 标签内的文本作为 JavaScript 函数返回到 render 函数。

    export default function App() {
      return <h1 className="greeting">{"Hello, this is a JSX Code!"}</h1>;
    }
    

    如果你不使用 JSX 语法,那么相应的 JavaScript 代码应如下编写:

    import { createElement } from "react";
    
    export default function App() {
      return createElement(
        "h1",
        { className: "greeting" },
        "Hello, this is a JSX Code!"
      );
    }
    
    查看类组件形式

    class App extends React.Component {
      render() {
        return <h1 className="greeting">{"Hello, this is a JSX Code!"}</h1>;
      }
    }
    

    注意: JSX 比 HTML 更严格。

    ⬆ 返回顶部

  5. Element 和 Component 有什么区别?

    Element(元素):

    • React Element 是一个普通的 JavaScript 对象,它描述了你希望在 UI 上看到的内容。它表示特定时间点的 DOM 节点或组件。

    • 元素是不可变的:一旦创建,就不能更改它们的属性。相反,你可以创建新的元素来反映更新。

    • 元素可以通过它们的 props 嵌套在其他元素中。

    • 创建元素是一个快速、轻量级的操作——它不会直接创建任何实际的 DOM 节点或将任何内容渲染到屏幕上。

      示例(不使用 JSX):

      const element = React.createElement("button", { id: "login-btn" }, "Login");
      

      等效的 JSX 语法:

      <button id="login-btn">Login</button>
      

      React.createElement 返回的对象:

      {
        type: 'button',
        props: {
          id: 'login-btn',
          children: 'Login'
        }
      }
      

      然后元素被传递给 React DOM 渲染器(例如 ReactDOM.render()),后者将它们转换为实际的 DOM 节点。


    Component(组件):

    • Component 是一个函数或类,它返回一个元素(或元素树)来描述 UI 的一部分。组件可以接受输入(称为 props)并管理自己的状态(对于类组件或使用 Hooks 的函数组件)。

    • 组件允许你将 UI 拆分为独立、可重用的部分,每个部分都是隔离且可组合的。

    • 你可以使用函数或类来定义组件:

      示例(带 JSX 的函数组件):

      const Button = ({ handleLogin }) => (
        <button id="login-btn" onClick={handleLogin}>
          Login
        </button>
      );
      

      当 JSX 被编译时,它会转换为 React.createElement 调用树:

      const Button = ({ handleLogin }) =>
        React.createElement(
          "button",
          { id: "login-btn", onClick: handleLogin },
          "Login"
        );
      

    总结:

    • Elements 是 React 中最小的构建块——描述你想要看到的内容的对象。
    • Components 是返回元素的函数或类,它们封装了 UI 部分的逻辑、结构和行为。

    可以将 elements 视为创建 UI 的指令,而 components 则是结合了逻辑和结构来生成这些指令的可重用蓝图。

    ⬆ 返回顶部

  6. 如何在 React 中创建组件?

    组件是在 React 中创建用户界面(UI)的构建块。有两种可能的方式来创建组件。

    1. 函数组件: 这是创建组件最简单的方式。它们是纯 JavaScript 函数,接受 props 对象作为唯一的参数,并返回 React 元素以渲染输出:

      function Greeting({ message }) {
        return <h1>{`Hello, ${message}`}</h1>;
      }
      
    2. 类组件: 你也可以使用 ES6 类来定义组件。上面的函数组件可以写成类组件:

      class Greeting extends React.Component {
        render() {
          return <h1>{`Hello, ${this.props.message}`}</h1>;
        }
      }
      

    ⬆ 返回顶部

  7. 什么时候使用类组件而不是函数组件?

    在 Hooks 出现之后(即 React 16.8 及以后版本),在 React 中总是建议优先使用函数组件而不是类组件。因为你可以在函数组件中使用状态、生命周期方法和其他以前仅在类组件中可用的功能。

    但即使如此,仍有两个理由使用类组件而不是函数组件。

    1. 如果你需要某个 React 功能,而其对应的函数组件等效实现尚未出现,例如错误边界。
    2. 在旧版本中,如果组件需要 状态或生命周期方法,那么你需要使用类组件。

    因此,这个问题的总结如下:

    使用函数组件:

    • 如果你不需要状态或生命周期方法,并且你的组件纯粹是展示性的。
    • 为了简洁性、可读性和现代代码实践,特别是结合使用 React Hooks 进行状态和副作用管理时。

    使用类组件:

    • 如果你需要管理状态或使用生命周期方法。
    • 在需要向后兼容或与旧代码集成的场景中。

    注意: 你也可以使用可重用的第三方 react error boundary 组件,而无需编写任何类。也就是说,不需要为错误边界使用类组件。

    上述库中错误边界的用法非常简单。

    使用 react-error-boundary 时的注意事项: ErrorBoundary 是一个客户端组件。你只能向它传递可序列化的 props,或者在具有 "use client"; 指令的文件中使用它。

    "use client";
    
    import { ErrorBoundary } from "react-error-boundary";
    
    <ErrorBoundary fallback={<div>Something went wrong</div>}>
      <ExampleApplication />
    </ErrorBoundary>;
    

    ⬆ 返回顶部

  8. 什么是纯组件(Pure Components)?

    纯组件是对于相同的状态和 props 会渲染相同输出的组件。在函数组件中,你可以通过使用记忆化的 React.memo() API 包装组件来实现这些纯组件。此 API 通过使用浅层比较来比较先前的 props 和新的 props,从而防止不必要的重新渲染。因此,它有助于性能优化。

    但同时,它不会比较先前的状态和当前的状态,因为当你再次设置相同的状态时,函数组件本身默认会防止不必要的渲染。

    记忆化组件的语法表示如下:

    const MemoizedComponent = memo(SomeComponent, arePropsEqual?);
    

    下面是子组件(即 EmployeeProfile)如何防止父组件(即 EmployeeRegForm)传递相同 props 时的重新渲染的示例。

    import { memo, useState } from "react";
    
    const EmployeeProfile = memo(function EmployeeProfile({ name, email }) {
      return (
        <>
          <p>Name:{name}</p>
          <p>Email: {email}</p>
        </>
      );
    });
    export default function EmployeeRegForm() {
      const [name, setName] = useState("");
      const [email, setEmail] = useState("");
      return (
        <>
          <label>
            Name:{" "}
            <input value={name} onChange={(e) => setName(e.target.value)} />
          </label>
          <label>
            Email:{" "}
            <input value={email} onChange={(e) => setEmail(e.target.value)} />
          </label>
          <hr />
          <EmployeeProfile name={name} />
        </>
      );
    }
    

    在上面的代码中,email prop 没有传递给子组件。因此,email prop 的变化不会导致重新渲染。

    在类组件中,继承 React.PureComponent 而不是 React.Component 的组件成为纯组件。当 props 或 state 发生变化时,PureComponent 将通过调用 shouldComponentUpdate() 生命周期方法对 props 和 state 进行浅层比较。

    注意: React.memo() 是一个高阶组件。

    ⬆ 返回顶部

  9. React 中的 state 是什么?

    组件的 State 是一个对象,它保存了一些可能在组件生命周期内发生变化的信息。重要的一点是,每当 state 对象发生变化时,组件就会重新渲染。始终建议使我们的 state 尽可能简单,并尽量减少有状态组件的数量。

    state

    让我们以一个带有 message state 的 User 组件为例。这里,useState Hook 用于向 User 组件添加 state,它返回一个数组,其中包含当前 state 和更新它的函数。

    import { useState } from "react";
    
    function User() {
      const [message, setMessage] = useState("Welcome to React world");
    
      return (
        <div>
          <h1>{message}</h1>
        </div>
      );
    }
    

    每当 React 调用你的组件或访问 useState Hook 时,它都会为该特定渲染提供一个 state 的快照。

    查看类组件形式

    import React from "react";
    class User extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          message: "Welcome to React world",
        };
      }
    
      render() {
        return (
          <div>
            <h1>{this.state.message}</h1>
          </div>
        );
      }
    }
    

    State 类似于 props,但它是私有的,完全由组件控制,即,直到所有者组件决定传递它,它才对任何其他组件可访问。

    ⬆ 返回顶部

  10. React 中的 props 是什么?

    Props 是组件的输入。它们是单个值或包含一组值的对象,在创建组件时传递给组件,类似于 HTML 标签属性。在这里,数据从父组件传递到子组件。

    React 中 props 的主要目的是提供以下组件功能:

    1. 向组件传递自定义数据。
    2. 触发状态变化。
    3. 在组件的 render() 方法内部通过 this.props.reactProp 使用。

    例如,让我们创建一个带有 reactProp 属性的元素:

    <Element reactProp={"1"} />
    

    这个 reactProp(或任何你想出的名称)属性名随后成为附加到 React 原生 props 对象的属性,该对象最初存在于使用 React 库创建的所有组件上。

    props.reactProp;
    

    例如,函数组件中 props 的用法如下所示:

    import React from "react";
    import ReactDOM from "react-dom";
    
    const ChildComponent = (props) => {
      return (
        <div>
          <p>{props.name}</p>
          <p>{props.age}</p>
          <p>{props.gender}</p>
        </div>
      );
    };
    
    const ParentComponent = () => {
      return (
        <div>
          <ChildComponent name="John" age="30" gender="male" />
          <ChildComponent name="Mary" age="25" geneder="female" />
        </div>
      );
    };
    

可以使用 ES6(ECMAScript 2015)中的解构特性直接访问 props 对象中的属性。当未指定 prop 值时,也可以回退到默认值。上面的子组件可以简化如下。

const ChildComponent = ({ name, age, gender = "male" }) => {
  return (
    <div>
      <p>{name}</p>
      <p>{age}</p>
      <p>{gender}</p>
    </div>
  );
};

注意: 如果你传入 null0 值,将不会使用默认值。也就是说,只有当未传入 prop 值或传入了 undefined 值时,才会使用默认值。

查看类 在类组件中访问 Props 的方式如下
import React from "react";
import ReactDOM from "react-dom";

class ChildComponent extends React.Component {
  render() {
    return (
      <div>
        <p>{this.props.name}</p>
        <p>{this.props.age}</p>
        <p>{this.props.gender}</p>
      </div>
    );
  }
}

class ParentComponent extends React.Component {
  render() {
    return (
      <div>
        <ChildComponent name="John" age="30" gender="male" />
        <ChildComponent name="Mary" age="25" gender="female" />
      </div>
    );
  }
}

⬆ Back to Top

  1. state 和 props 有什么区别?

    在 React 中,stateprops 都是普通的 JavaScript 对象,但它们的用途和行为各不相同:

    State

    • 定义:
      State 是组件内部管理的数据结构。它表示在组件生命周期内可能发生变化的信息。
    • 可变性:
      State 是可变的,这意味着可以使用 setter 函数(类组件中的 setState 或函数组件中 useState 返回的更新函数)来修改它。
    • 作用域:
      State 是定义它的组件所私有的。只有该组件可以修改自己的 state。
    • 用途:
      State 通常用于需要响应用户操作、网络响应或其他动态事件而变化的数据。
    • 重新渲染:
      更新 state 会触发组件及其子组件的重新渲染。

    Props

    • 定义:
      Props(“properties” 的缩写)是组件的输入,由其父组件提供。
    • 可变性:
      Props 是只读的。组件不能修改自己的 props;从组件的角度来看,它们是不可变的。
    • 作用域:
      Props 用于在组件树中向下传递数据和事件处理函数,使父组件能够配置或与子组件通信。
    • 用途:
      Props 通常用于使组件可重用和可配置。它们允许同一个组件使用不同的数据或行为进行渲染。
    • 类比:
      可以将 props 看作函数的参数,而 state 则类似于函数内部声明的变量。

    总结表格

    特性 State Props
    管理方 组件自身 父组件
    可变性 否(只读)
    作用域 组件内部 从父组件传递到子组件
    用途 管理动态数据和 UI 变化 配置和自定义组件
    更新方式 使用 setState/useState 组件自身无法更新

    ⬆ Back to Top

  2. HTML 和 React 事件处理有什么区别?

    以下是 HTML 和 React 事件处理之间的一些主要区别:

    1. 在 HTML 中,事件名称通常按照约定使用小写

      <button onclick="activateLasers()"></button>
      

      而在 React 中,事件名称遵循驼峰式命名约定:

      <button onClick={activateLasers}>
      
    2. 在 HTML 中,可以返回 false 来阻止默认行为:

      <a
        href="#"
        onclick='console.log("链接被点击。"); return false;'
      />
      

      而在 React 中,必须显式调用 preventDefault()

      function handleClick(event) {
        event.preventDefault();
        console.log("链接被点击。");
      }
      
    3. 在 HTML 中,需要通过添加 () 来调用函数。 而在 React 中,不应在函数名后添加 ()。(例如,参考第一点中的 "activateLasers" 函数)

    ⬆ Back to Top

  3. React 中的合成事件是什么?

    SyntheticEvent 是浏览器原生事件的跨浏览器包装器。它的 API 与浏览器原生事件相同,包括 stopPropagation()preventDefault(),不同的是这些事件在所有浏览器中的工作方式都一致。可以通过 nativeEvent 属性从合成事件中直接访问原生事件。

    让我们以一个具有获取所有原生事件属性功能的 BookStore 标题搜索组件为例:

    function BookStore() {
      function handleTitleChange(e) {
        console.log("新标题是:", e.target.value);
        console.log('合成事件:', e); // React 合成事件
        console.log('原生事件:', e.nativeEvent); // 浏览器原生事件
        e.stopPropagation();
        e.preventDefault();
      }
    
      return <input name="title" onChange={handleTitleChange} />;
    }
    

    常见的合成事件列表包括:

    • onClick
    • onChange
    • onSubmit
    • onKeyDownonKeyUp
    • onFocusonBlur
    • onMouseEnteronMouseLeave
    • onTouchStartonTouchEnd

    ⬆ Back to Top

  4. 什么是内联条件表达式?

    你可以使用 JS(以及 React 中的 JSX)中可用的 if 语句三元表达式来有条件地执行或渲染表达式。除了这些方法,你还可以通过将任何表达式用花括号包裹,然后跟上 JS 逻辑运算符 &&,将其嵌入到 JSX 中。这有助于在单行内有条件地渲染元素,并且常用于简洁的逻辑,尤其是在 JSX 渲染中。

    <h1>Hello!</h1>;
    {
      messages.length > 0 && !isLogin ? (
        <h2>你有 {messages.length} 条未读消息。</h2>
      ) : (
        <h2>你没有未读消息。</h2>
      );
    }
    

    ⬆ Back to Top

  5. 什么是“key” prop?在元素数组中使用它有什么好处?

    key 是在映射数组以渲染数据时应该包含的一个特殊属性。Key prop 帮助 React 识别哪些项目已更改、已添加或已删除。

    键在其兄弟元素中应该是唯一的。大多数情况下,我们使用数据中的 ID 作为 key

    const todoItems = todos.map((todo) => <li key={todo.id}>{todo.text}</li>);
    

    当没有用于渲染项目的稳定 ID 时,你可以将项目的 index 作为 key 作为最后的手段:

    const todoItems = todos.map((todo, index) => (
      <li key={index}>{todo.text}</li>
    ));
    

    key 的好处:

    • 使 React 能够高效地更新和重新渲染组件。
    • 通过在可能时重用组件来防止不必要的重新渲染。
    • 帮助正确维护列表项的内部状态。

    注意:

    1. 如果项目的顺序可能改变,不建议使用 indexes 作为 keys。这可能会对性能产生负面影响,并可能导致组件状态出现问题。
    2. 如果你将列表项提取为单独的组件,那么应将 keys 应用于列表组件,而不是 li 标签。
    3. 如果列表项上没有 key prop,控制台会显示警告消息。
    4. key 属性接受字符串或数字,并在内部将其转换为字符串类型。
    5. 不要动态生成 key,例如 key={Math.random()}。因为在重新渲染之间,键将永远不匹配,并且每次都会创建 DOM。

    ⬆ Back to Top

  6. 什么是虚拟 DOM(Virtual DOM)?

    虚拟 DOM(VDOM)是一种轻量级的、内存中的 真实 DOM 表示,被 React 等库用于优化 UI 渲染。UI 的表示保存在内存中,并与“真实”DOM 同步。这是在调用 render 函数和在屏幕上显示元素之间发生的一个步骤。整个过程称为 协调(reconciliation)

    ⬆ Back to Top

  7. 虚拟 DOM 是如何工作的?

    虚拟 DOM 的工作分为五个简单步骤。

    1. 初始渲染
    当 UI 组件首次渲染时,它会返回 JSX。React 使用此结构创建一个虚拟 DOM 树,这是实际 DOM 的轻量级副本。然后,这个虚拟 DOM 用于在浏览器中构建和渲染真实 DOM。

    2. 状态或属性变化
    当组件的 state 或 props 发生变化时,React 会创建一个反映更新后 UI 的新虚拟 DOM。但是,它不会立即更新真实 DOM;相反,它在内存中工作,为高效更新做准备。

    vdom

    3. 差异算法(Diffing Algorithm)
    然后,React 使用称为差异比较(diffing)的过程将新的虚拟 DOM 与之前的虚拟 DOM 进行比较。它确定两个版本之间发生了什么变化,并识别所需的最小更新集。

    vdom2

    4. 协调(Reconciliation)
    根据差异比较的结果,React 决定应该更新真实 DOM 的哪些部分。它避免重新渲染整个 DOM,而是只更新实际发生变化的元素。

    vdom3

    5. 高效的 DOM 更新
    整个过程——使用虚拟 DOM、差异比较和选择性更新——使得 UI 渲染比直接操作真实 DOM 更快、更高效。

    ⬆ Back to Top

  8. 影子 DOM(Shadow DOM)和虚拟 DOM(Virtual DOM)有什么区别?

    影子 DOM 是一种浏览器技术,主要用于 Web 组件 中的变量和 CSS 作用域隔离。虚拟 DOM 是 JavaScript 库在浏览器 API 之上实现的一个概念。

    下面以表格形式展示主要区别:

    特性 影子 DOM 虚拟 DOM
    目的 Web 组件的封装 高效的 UI 渲染
    管理方 浏览器 JS 框架(如 React)
    DOM 类型 真实 DOM 的一部分(作用域隔离) 内存中的表示
    封装性
    使用场景 Web 组件、作用域样式 UI 差异比较和最小 DOM 更新

    ⬆ Back to Top

  9. 什么是 React Fiber?

    React Fiber 是 React 中的新协调引擎,在 React 16 中引入。它是对 React 核心渲染和更新 UI 算法(旧的基于栈的算法)的完全重写。Fiber 增强了 React 处理异步渲染优先级更新(为不同类型的更新分配优先级)和中断(暂停、中止或重用工作的能力)渲染工作的能力,从而实现更流畅、更响应的用户界面。

    ⬆ Back to Top

  10. React Fiber 的主要目标是什么?

    React Fiber 的目标是提高其在动画、布局和手势等领域的适用性。其核心特性是增量渲染:能够将渲染工作拆分为多个块,并在多个帧中分散进行。

    其主要目标包括:

    • 增量渲染 – 将工作分解为块,以实现更平滑的更新。
    • 可中断渲染 – 暂停和恢复渲染以保持 UI 的响应性。
    • 优先级排序 – 在低优先级更新之前处理高优先级更新(例如动画)。
    • 并发支持 – 允许同时处理多个 UI 版本。
    • 更好的错误处理 – 支持组件级别的错误边界。
    • Suspense 支持 – 允许在渲染前等待异步数据。
    • 改进的开发工具 – 实现更好的调试和性能跟踪。

    ⬆ Back to Top

  11. 什么是受控组件?

    受控组件是一个 React 组件,它完全使用 React 的内部状态机制管理表单元素的状态(例如 <input><textarea><select> 等元素)。也就是说,该组件不管理自己的内部状态——相反,React 充当表单数据的单一数据源。

    受控组件的实现步骤如下:

    1. 在函数组件中使用 useState hooks 初始化状态,或在类组件的构造函数中初始化状态。
    2. 将表单元素的值设置为相应的状态变量。
    3. 创建一个事件处理函数(onChange),通过 useState 的更新函数或类组件的 setState 来处理用户输入的变化。
    4. 将上述事件处理函数附加到表单元素的 change 或 click 事件上

    注意: 每次输入值变化时,React 都会重新渲染组件。

例如,名称输入字段通过 handleChange 事件处理函数更新用户名,如下所示:

   import React, { useState } from "react";

   function UserProfile() {
     const [username, setUsername] = useState("");

     const handleChange = (e) => {
       setUsername(e.target.value);
     };

     return (
       <form>
         <label>
           Name:
           <input type="text" value={username} onChange={handleChange} />
         </label>
       </form>
     );
   }

在这些组件中,DOM 并不存储实际数据,而是由 React 来管理数据。

优势:

  • 易于实现验证条件格式化实时反馈
    • 完全控制表单数据。
    • 数据集中在组件的 state 中,因此更易于测试和调试。

⬆ 返回顶部

  1. 什么是非受控组件?

    非受控组件是指那些(如 <input><textarea><select> 等)通过 DOM 内部管理自身状态,而非通过 React state 管理状态的表单元素。 当你需要获取其当前值时,可以使用 ref 查询 DOM。这有点类似于传统的 HTML 方式。

    实现非受控组件需要以下步骤:

    1. 在函数组件中使用 useRef React Hook 创建一个 ref,或在类组件中使用 React.createRef() 创建 ref。
    2. 将此 ref 附加到表单元素上。
    3. 可以在事件处理函数中,或在类组件的 componentDidMount 生命周期方法中,通过 ref 直接访问表单元素的值。

    在下面的 UserProfile 组件中,username 输入框通过 ref 进行访问。

    import React, { useRef } from "react";
    
    function UserProfile() {
      const usernameRef = useRef(null);
    
      const handleSubmit = (event) => {
        event.preventDefault();
        console.log("The submitted username is: " + usernameRef.current.value);
      };
    
      return (
        <form onSubmit={handleSubmit}>
          <label>
            Username:
            <input type="text" ref={usernameRef} />
          </label>
          <button type="submit">Submit</button>
        </form>
      );
    }
    

    注意: 在这里,DOM 负责管理值。React 仅在需要时(通过 ref)访问该值。

    优势:

    • 代码量更少 — 无需使用 useStateonChange
    • 适用于快速搭建表单或与非 React 代码集成的场景。
    • 在非常大型的表单中,性能会略好一些(重新渲染次数更少)。

    在大多数情况下,建议使用受控组件来实现表单。在受控组件中,表单数据由 React 组件处理。另一种选择是非受控组件,其中表单数据由 DOM 本身处理。

    查看类组件示例

    class UserProfile extends React.Component {
      constructor(props) {
        super(props);
        this.handleSubmit = this.handleSubmit.bind(this);
        this.input = React.createRef();
      }
    
      handleSubmit(event) {
        alert("A name was submitted: " + this.input.current.value);
        event.preventDefault();
      }
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <label>
              {"Name:"}
              <input type="text" ref={this.input} />
            </label>
            <input type="submit" value="Submit" />
          </form>
        );
      }
    }
    

⬆ 返回顶部

  1. createElement 和 cloneElement 有什么区别?

    React.createElementReact.cloneElement 都用于处理 React 元素,但它们的用途不同。

    createElement:

    从头创建一个新的 React 元素。JSX 元素会被转译为 React.createElement() 函数,以创建用于 UI 对象表示的 React 元素。 语法:

    React.createElement(type, props, ...children)
    

    示例:

    React.createElement('button', { className: 'btn' }, 'Click Me')
    

    cloneElement:

    cloneElement 方法用于克隆现有的 React 元素,并可选择性地添加或覆盖属性。

    语法:

    React.cloneElement(element, newProps, ...children)
    

    示例:

    const button = <button className="btn">Click Me</button>;
    const cloned = React.cloneElement(button, { className: 'btn-primary' });
    // 结果:<button className="btn-primary">Click Me</button>
    

    ⬆ 返回顶部

  2. React 中的“状态提升”是什么?

    当多个组件需要共享相同的变化数据时,建议将共享状态“提升”到它们最近的共同祖先组件。也就是说,如果两个子组件从其父组件共享相同的数据,那么应将状态移至父组件,而不是在两个子组件中都维护本地状态。

    ⬆ 返回顶部

  3. 什么是高阶组件?

    高阶组件(HOC)是一个接收组件并返回一个具有额外属性、行为或数据的新增强组件的函数。它是一种基于 React 组合特性的设计模式,允许在不修改组件内部的情况下跨多个组件重用逻辑。

    我们认为 HOC 是纯组件,因为它们不会修改或复制原始组件的行为——它们只是包装原始组件、增强它,并传递必要的属性。被包装的组件保持解耦且可重用。

    const EnhancedComponent = higherOrderComponent(WrappedComponent);
    

    让我们以 React 中的 withAuth 高阶组件(HOC)为例。这个 HOC 将检查用户是否已认证,如果已认证则渲染被包装的组件,如果未认证则重定向(或显示消息)。

    withAuth HOC 示例:

    import React from 'react';
    import { Navigate } from 'react-router-dom'; // 用于重定向(假设使用 React Router v6)
    
    const isAuthenticated = () => {
      // 例如,检查 localStorage 或上下文中的有效令牌
      return !!localStorage.getItem('authToken');
    };
    
    function withAuth(WrappedComponent) {
      return function AuthenticatedComponent(props) {
        if (!isAuthenticated()) {
          // 用户未认证,重定向到登录页面
          return <Navigate to="/login" replace />;
        }
    
        // 用户已认证,渲染被包装的组件
        return <WrappedComponent {...props} />;
      };
    }
    
    export default withAuth;
    

    用法

    import React from 'react';
    import withAuth from './withAuth';
    
    function Dashboard() {
      return <h1>欢迎来到仪表盘!</h1>;
    }
    
    // 用 withAuth HOC 包装 Dashboard
    export default withAuth(Dashboard);
    

    HOC 可用于许多场景:

    1. 代码重用、逻辑和引导抽象(例如,数据获取、权限控制、主题设置)。
    2. 渲染劫持(例如,条件渲染或布局更改)。
    3. 状态抽象和操作(例如,处理表单逻辑)。
    4. 属性操作(例如,注入额外属性或过滤属性)。

    React 生态系统中 HOC 的一些实际例子:

    1. react-redux 中的 connect()
    2. React Router v5 中的 withRouter()
    3. react-i18next 中的 withTranslation()
    4. Apollo client 中的 withApollo()
    5. Formik 库中的 withFormik
    6. styled components 中的 withTheme

    ⬆ 返回顶部

  4. 什么是 children 属性?

    children 属性是 React 中的一个特殊属性,用于在组件的开始和结束标签之间传递元素。它常用于布局和包装组件。

    children 属性的简单用法如下:

    function MyDiv({ children }){
        return (
          <div>
            {children}
          </div>;
        );
    }
    
    export default function Greeting() {
      return (
        <MyDiv>
          <span>{"Hello"}</span>
          <span>{"World"}</span>
        </MyDiv>
      );
    }
    

    这里,<MyDiv>...</MyDiv> 内部的所有内容都作为 children 传递给自定义 div 组件。

    children 可以是文本、JSX 元素、片段、数组和函数(用于高级用例,如渲染属性)。

    查看类组件用法

    const MyDiv = React.createClass({
      render: function () {
        return <div>{this.props.children}</div>;
      },
    });
    
    ReactDOM.render(
      <MyDiv>
        <span>{"Hello"}</span>
        <span>{"World"}</span>
      </MyDiv>,
      node
    );
    

    注意: 旧版 React API 中有几个方法可用于处理此属性。这些方法包括 React.Children.mapReact.Children.forEachReact.Children.countReact.Children.onlyReact.Children.toArray

    ⬆ 返回顶部

  5. 如何在 React 中写注释?

    React/JSX 中的注释类似于 JavaScript 的多行注释,但需要用花括号包裹。

    单行注释:

    <div>
      {/* 单行注释(在原生 JavaScript 中,单行注释用双斜杠 // 表示) */}
      {`Welcome ${user}, let's play React`}
    </div>
    

    多行注释:

    <div>
      {/* 多行注释用于
       多行内容 */}
      {`Welcome ${user}, let's play React`}
    </div>
    

    你可以在 JS 逻辑、钩子和函数中使用 ///* */

    ⬆ 返回顶部

  6. 什么是协调(Reconciliation)?

    协调是 React 更新浏览器 DOM 并使 React 运行更快的过程。React 使用差异算法,使组件更新具有可预测性且更快。当组件有更新时,React 会首先计算真实 DOM和 DOM 副本(虚拟 DOM)之间的差异。 React 存储浏览器 DOM 的一个副本,称为虚拟 DOM。当我们进行更改或添加数据时,React 会创建一个新的虚拟 DOM 并将其与之前的虚拟 DOM 进行比较。这种比较由差异算法完成。 现在 React 会将虚拟 DOM 与真实 DOM 进行比较。它找出已更改的节点,并仅更新真实 DOM 中已更改的节点,其余节点保持不变。这个过程称为协调

    ⬆ 返回顶部

  7. lazy 函数支持命名导出吗?

    不,目前 React.lazy 函数仅支持默认导出。如果你想导入命名导出的模块,可以创建一个中间模块,将其重新导出为默认导出。这也确保了树摇(tree shaking)能够正常工作,不会引入未使用的组件。 让我们以一个导出多个命名组件的组件文件为例:

    // MoreComponents.js
    export const SomeComponent = /* ... */;
    export const UnusedComponent = /* ... */;
    

    然后在中间文件 IntermediateComponent.js 中重新导出 MoreComponents.js 的组件:

    // IntermediateComponent.js
    export { SomeComponent as default } from "./MoreComponents.js";
    

    现在你可以使用 lazy 函数导入该模块,如下所示:

    import React, { lazy } from "react";
    const SomeComponent = lazy(() => import("./IntermediateComponent.js"));
    

    ⬆ 返回顶部

  8. React 为什么使用 className 而不是 class 属性?

    React 使用 className 而不是 class,是因为与 JavaScript 中的 class 关键字存在命名冲突。

    1. class 是 JavaScript 中的保留关键字 在 JavaScript 中,class 用于定义 ES6 类:

      class Person {
        constructor(name) {
          this.name = name;
        }
      }
      

      如果你尝试将 class 用作变量名或属性名,会抛出语法错误。由于 JSX 本质上是带有类 XML 语法的 JavaScript,直接在 JSX 中使用 class 会导致解析器出错。

    2. JSX 是 JavaScript

      当你编写如下 JSX 时:

      <div class="btn">Click</div>
      

      它会被编译为:

      React.createElement('div', { class: 'btn' }, 'Click');
      

      但是在对象字面量上下文中,class 是无效的(因为它与 JS 关键字冲突),因此 React 改用 className。

      <div className="btn">Click</div>
      

      这会被编译为:

      React.createElement('div', { className: 'btn' }, 'Click');
      

      React 随后会在最终的 HTML DOM 中将 className 转换为 class

    3. 与 DOM API 保持一致 在原生 JavaScript 中,你使用以下方式操作元素的类:

      element.className = 'my-class';
      

      React 遵循此约定,与 DOM API 的属性名称保持一致,而不是 HTML 的属性名称。

    ⬆ 返回顶部

  9. 什么是片段(Fragments)?

    在 React 中,组件返回多个元素是一种常见的模式或做法。片段允许你将子元素列表分组,而无需向 DOM 添加额外的节点。 你需要使用 <Fragment> 或更简短的空标签语法 (<></>)。

    以下是如何在 Story 组件中使用片段的示例。

    function Story({ title, description, date }) {
      return (
        <Fragment>
          <h2>{title}</h2>
          <p>{description}</p>
          <p>{date}</p>
        </Fragment>
      );
    }
    

    也可以在循环中渲染片段列表,并必须提供 key 属性。

    function StoryBook() {
      return stories.map((story) => (
        <Fragment key={story.id}>
          <h2>{story.title}</h2>
          <p>{story.description}</p>
          <p>{story.date}</p>
        </Fragment>
      ));
    }
    

    通常,除非需要 key 属性,否则你不需要使用 <Fragment>。更简短的语法用法如下:

    function Story({ title, description, date }) {
      return (
        <>
          <h2>{title}</h2>
          <p>{description}</p>
          <p>{date}</p>
        </>
      );
    }
    

    ⬆ 返回顶部

  10. 为什么片段比容器 div 更好?

    以下是倾向于使用片段而非容器 DOM 元素的原因列表:

    1. 片段不会创建额外的 DOM 节点,因此速度更快,内存占用更少。这在非常大且深的树结构上才有实际好处。
    2. 某些 CSS 机制(如 FlexboxCSS Grid)具有特殊的父子关系,在中间添加 div 会难以保持所需的布局。
    3. DOM 检查器的杂乱程度更低。

    ⬆ 返回顶部

  11. React 中的门户(Portals)是什么?

    门户(Portal)是 React 的一项功能,它允许将子元素渲染到存在于父组件 DOM 层次结构之外的 DOM 节点中,同时仍然保留 React 组件层次结构。门户有助于避免 CSS 堆叠问题——例如,position: fixed 的元素在具有 transform 的父元素内部可能无法按预期工作。门户通过将内容(如模态框或工具提示)渲染到此类受限 DOM 上下文之外来解决此问题。

    ReactDOM.createPortal(child, container);
    
    • child:任何有效的 React 节点(例如,JSX、字符串、片段)。
    • container:一个真实的 DOM 节点(例如,document.getElementById('modal-root'))。

    即使内容在 DOM 的其他位置渲染,它在 React 中仍然表现得像一个普通的子组件。它可以访问上下文、状态和事件处理。

    示例:模态框

    function Modal({ children }) {
      return ReactDOM.createPortal(
        <div className="modal">{children}</div>,
        document.body)
      );
    }
    

    上面的代码会将模态框内容渲染到 HTML 中的 body 元素内,而不是组件通常所在的位置。

    ⬆ 返回顶部

  12. 什么是无状态组件?

    如果组件的行为与其状态无关,则它可以是无状态组件。你可以使用函数或类来创建无状态组件。但除非你需要在组件中使用生命周期钩子,否则应该选择函数组件。如果你决定在这里使用函数组件,会有很多好处:它们易于编写、理解和测试,速度稍快,并且你可以完全避免使用 this 关键字。

    ⬆ 返回顶部

  13. 什么是有状态组件?

    如果组件的行为依赖于组件的状态,则它可以被称为有状态组件。这些有状态组件要么是使用钩子的函数组件,要么是类组件

    让我们以一个函数式有状态组件为例,它基于点击事件更新状态:

    import React, {useState} from 'react';
    
    const App = (props) => {
    const [count, setCount] = useState(0);
    const handleIncrement = () => {
      setCount(count + 1);
    }
    
    return (
      <>
        <button onClick={handleIncrement}>Increment</button>
        <span>Counter: {count}</span>
      </>
      )
    }
    
    查看类组件

    等效的类有状态组件,其状态在 `constructor` 中初始化。

    class App extends Component {
      constructor(props) {
        super(props);
        this.state = { count: 0 };
        this.handleIncrement = this.handleIncrement.bind(this);
      }
    
      handleIncrement() {
        this.setState({ count: this.state.count + 1 });
      }
    
      render() {
        return (
          <>
            <button onClick={this.handleIncrement}>Increment</button>
            <span>Count: {this.state.count}</span>
          </>
        );
      }
    }
    

    ⬆ 返回顶部

  14. 如何在 React 中对属性(props)进行验证?

    当应用程序在开发模式下运行时,React 会自动检查我们在组件上设置的所有属性,以确保它们具有正确的类型。如果类型不正确,React 会在控制台中生成警告消息。由于性能影响,它在生产模式下是禁用的。必需的属性用 isRequired 定义。

    预定义的属性类型集:

    1. PropTypes.number
    2. PropTypes.string
    3. PropTypes.array
    4. PropTypes.object
    5. PropTypes.func
    6. PropTypes.node
    7. PropTypes.element
    8. PropTypes.bool
    9. PropTypes.symbol
    10. PropTypes.any

    我们可以为 User 组件定义 propTypes 如下:

    import React from "react";
    import PropTypes from "prop-types";
    
    class User extends React.Component {
      static propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number.isRequired,
      };
    
      render() {
        return (
          <>
            <h1>{`Welcome, ${this.props.name}`}</h1>
            <h2>{`Age, ${this.props.age}`}</h2>
          </>
        );
      }
    }
    

    注意: 在 React v15.5 中,PropTypes 已从 React.PropTypes 移至 prop-types 库。

    现代推荐: 虽然 PropTypes 仍然受支持,但TypeScript 现在是 React 应用程序中类型检查的行业标准。考虑使用 TypeScript 以获得更好的类型安全性、IDE 支持和编译时错误检测。

    等效的函数组件

    import React from "react";
    import PropTypes from "prop-types";
    
    function User({ name, age }) {
      return (
        <>
          <h1>{`Welcome, ${name}`}</h1>
          <h2>{`Age, ${age}`}</h2>
        </>
      );
    }
    
    User.propTypes = {
      name: PropTypes.string.isRequired,
      age: PropTypes.number.isRequired,
    };
    

    现代 TypeScript 版本

    import React from "react";
    
    interface UserProps {
      name: string;
      age: number;
    }
    
    function User({ name, age }: UserProps) {
      return (
        <>
          <h1>{`Welcome, ${name}`}</h1>
          <h2>{`Age, ${age}`}</h2>
        </>
      );
    }
    

    ⬆ 返回顶部

  15. React 的优点是什么?

    以下是 React 的主要优点列表:

    1. 通过虚拟 DOM 提高应用程序性能。
    2. JSX 使代码易于阅读和编写。
    3. 它可以在客户端和服务器端(SSR)渲染。
    4. 易于与其他框架(Angular、Backbone)集成,因为它只是一个视图库。
    5. 易于使用 Jest 等工具编写单元测试和集成测试。

    ⬆ 返回顶部

  16. React 的局限性是什么?

    除了优点之外,React 也有一些局限性:

    1. React 只是一个视图库,不是一个完整的框架。
    2. 对于刚接触 Web 开发的初学者来说有一定的学习曲线。
    3. 将 React 集成到传统的 MVC 框架中需要一些额外的配置。
    4. 内联模板和 JSX 增加了代码的复杂性。
    5. 过多的小组件可能导致过度工程化或样板代码。

    ⬆ 返回顶部

  17. 静态类型检查的推荐方法有哪些?

    现代推荐(2026): TypeScript 是 React 应用程序中类型检查的行业标准。

    虽然 PropTypes(自 React v15.5 起,React.PropTypes 已移至 prop-types 包)仍可用于运行时类型检查,但它们有明显的局限性:

    • 仅在运行时检查类型
    • 无编译时错误
    • IDE 自动完成支持有限
    • 对复杂类型无推断能力

    TypeScript 现在是推荐的方法,因为它提供:

    • 编译时类型检查
    • 出色的 IDE 支持(带有 IntelliSense)
    • 类型推断和泛型
    • 更好的重构能力
    • 不断增长的生态系统和社区支持

    注意: Flow(Facebook 的类型检查器)的采用率一直在下降,在新项目中很少使用。

    TypeScript 示例:

    interface UserProps {
      name: string;
      age: number;
      isActive?: boolean;
    }
    
    function User({ name, age, isActive = true }: UserProps) {
      return <div>{name} - {age}</div>;
    }
    

    ⬆ 返回顶部

  18. react-dom 包的用途是什么?

    react-dom 包提供了特定于 DOM 的方法,可用于应用程序的顶层。大多数组件不需要使用此模块。该包的一些方法包括:

    1. render()
    2. hydrate()
    3. unmountComponentAtNode()
    4. findDOMNode()
    5. createPortal()

    ⬆ 返回顶部

  19. 什么是 ReactDOMServer?

ReactDOMServer 对象能让你将组件渲染为静态标记(通常用于 node 服务器)。该对象主要用于服务器端渲染(SSR)。以下方法可同时在服务器和浏览器环境中使用:

  1. renderToString()
  2. renderToStaticMarkup()

例如,你通常会运行一个基于 Node 的 Web 服务器,如 Express、Hapi 或 Koa,然后调用 renderToString 将根组件渲染为字符串,再将其作为响应发送。

// using Express
import { renderToString } from "react-dom/server";
import MyPage from "./MyPage";

app.get("/", (req, res) => {
  res.write(
    "<!DOCTYPE html><html><head><title>My Page</title></head><body>"
  );
  res.write('<div id="content">');
  res.write(renderToString(<MyPage />));
  res.write("</div></body></html>");
  res.end();
});

⬆ Back to Top

  1. 如何在 React 中使用 innerHTML?

dangerouslySetInnerHTML 属性是 React 中用于替代浏览器 DOM 中 innerHTML 的方案。和 innerHTML 一样,使用此属性存在跨站脚本(XSS)攻击的风险。你只需传递一个以 __html 为键、HTML 文本为值的对象即可。

在以下示例中,MyComponent 使用 dangerouslySetInnerHTML 属性来设置 HTML 标记:

function createMarkup() {
  return { __html: "First &middot; Second" };
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

⬆ Back to Top

  1. 如何在 React 中使用样式?

style 属性接受一个具有小驼峰命名(camelCased)属性的 JavaScript 对象,而非 CSS 字符串。这与 DOM 样式的 JavaScript 属性访问方式一致,更高效,且能防止 XSS 安全漏洞。

const divStyle = {
  color: "blue",
  backgroundImage: "url(" + imgUrl + ")",
};

function HelloWorldComponent() {
  return <div style={divStyle}>Hello World!</div>;
}

样式键采用小驼峰命名,以与 JavaScript 中访问 DOM 节点属性的方式保持一致(例如 node.style.backgroundImage)。

⬆ Back to Top

  1. React 中的事件有何不同?

在 React 元素中处理事件存在一些语法差异:

  1. React 事件处理函数采用小驼峰命名(camelCase),而非全小写。
  2. 在 JSX 中,你传递一个函数作为事件处理程序,而非字符串。

⬆ Back to Top

  1. 使用索引作为 key 有什么影响?

key 应该是稳定的、可预测的和唯一的,这样 React 才能跟踪元素。

在下面的代码片段中,每个元素的 key 基于其排列顺序,而非与其所表示的数据相关联。这限制了 React 可进行的优化,并可能在应用中产生令人困惑的 bug。

{
  todos.map((todo, index) => <Todo {...todo} key={index} />);
}

如果你使用元素数据作为唯一 key(假设 todo.id 对于此列表是唯一且稳定的),React 将能够在不需要过多重新评估元素的情况下对其进行重新排序。

{
  todos.map((todo) => <Todo {...todo} key={todo.id} />);
}

注意: 如果你根本不指定 key 属性,React 在遍历数据数组时会将索引用作 key 的值。

⬆ Back to Top

  1. 如何有条件地渲染组件?

在某些情况下,你希望根据某些状态渲染不同的组件。JSX 不会渲染 falseundefined,因此你可以使用条件短路运算,仅当特定条件为 true 时才渲染组件的特定部分。

const MyComponent = ({ name, address }) => (
  <div>
    <h2>{name}</h2>
    {address && <p>{address}</p>}
  </div>
);

如果你需要 if-else 条件,可以使用三元运算符

const MyComponent = ({ name, address }) => (
  <div>
    <h2>{name}</h2>
    {address ? <p>{address}</p> : <p>{"Address is not available"}</p>}
  </div>
);

⬆ Back to Top

  1. 为什么在 DOM 元素上展开 props 时需要小心?

当我们展开 props 时,可能会添加未知的 HTML 属性,这是一种不良实践。相反,我们可以使用带有 ...rest 运算符的 prop 解构,这样只会添加所需的 props。

例如:

const ComponentA = () => (
  <ComponentB isDisplay={true} className={"componentStyle"} />
);

const ComponentB = ({ isDisplay, ...domProps }) => (
  <div {...domProps}>{"ComponentB"}</div>
);

⬆ Back to Top

  1. 如何记忆化(memoize)一个组件?

有一些记忆化库可用于函数组件。

例如,moize 库可以在另一个组件中记忆化组件。

import moize from "moize";
import Component from "./components/Component"; // 此模块导出一个非记忆化的组件

const MemoizedFoo = moize.react(Component);

const Consumer = () => {
  <div>
    {"I will memoize the following entry:"}
    <MemoizedFoo />
  </div>;
};

更新: 从 React v16.6.0 开始,我们有了 React.memo。它提供了一个高阶组件,该组件会记忆化组件,除非 props 发生变化。要使用它,只需在使用组件之前用 React.memo 包装它。

const MemoComponent = React.memo(function MemoComponent(props) {
  /* 使用 props 进行渲染 */
});
或者;
export default React.memo(MyFunctionComponent);

⬆ Back to Top

  1. 如何实现服务器端渲染(SSR)?

React 已经具备在 Node 服务器上进行渲染的能力。有一个特殊版本的 DOM 渲染器可用,它遵循与客户端相同的模式。

import ReactDOMServer from "react-dom/server";
import App from "./App";

ReactDOMServer.renderToString(<App />);

此方法会将常规 HTML 输出为字符串,然后可以将其作为服务器响应的一部分放置在页面主体中。在客户端,React 会检测到预渲染的内容,并无缝地从停止的地方继续执行。

⬆ Back to Top

  1. 如何在 React 中启用生产模式?

你应该使用 Webpack 的 DefinePlugin 方法将 NODE_ENV 设置为 production,这样可以去除诸如 propType 验证和额外警告之类的内容。除此之外,如果你对代码进行压缩(例如,使用 Uglify 的死代码消除功能来去除仅开发环境的代码和注释),将会显著减小你的 bundle 大小。

⬆ Back to Top

  1. Hooks 是否取代了 render props 和高阶组件?

render props 和高阶组件都只渲染单个子组件,但在大多数情况下,Hooks 通过减少组件树中的嵌套,提供了一种更简单的方式来实现相同的功能。

⬆ Back to Top

  1. 什么是切换组件(switching component)?

切换组件是一个可以渲染多个组件中的一个的组件。我们需要使用对象将 prop 值映射到组件。

例如,一个根据 page prop 显示不同页面的切换组件:

import HomePage from "./HomePage";
import AboutPage from "./AboutPage";
import ServicesPage from "./ServicesPage";
import ContactPage from "./ContactPage";

const PAGES = {
  home: HomePage,
  about: AboutPage,
  services: ServicesPage,
  contact: ContactPage,
};

const Page = (props) => {
  const Handler = PAGES[props.page] || ContactPage;

  return <Handler {...props} />;
};

// PAGES 对象的键可以用在 propTypes 中,以捕获开发时错误。
Page.propTypes = {
  page: PropTypes.oneOf(Object.keys(PAGES)).isRequired,
};

⬆ Back to Top

  1. React 支持哪些指针事件(Pointer Events)?

指针事件提供了一种统一的方式来处理所有输入事件。在过去,我们只有鼠标和相应的事件监听器来处理它们,但现在我们有许多与鼠标无关的设备,例如带有触摸表面的手机或手写笔。需要记住的是,这些事件仅在支持指针事件规范的浏览器中有效。

以下事件类型现在在React DOM中可用:

  1. onPointerDown
  2. onPointerMove
  3. onPointerUp
  4. onPointerCancel
  5. onGotPointerCapture
  6. onLostPointerCapture
  7. onPointerEnter
  8. onPointerLeave
  9. onPointerOver
  10. onPointerOut

⬆ Back to Top

  1. 为什么组件名称应该以大写字母开头?

如果你使用 JSX 渲染组件,该组件的名称必须以大写字母开头,否则 React 会将其视为未识别的标签并抛出错误。此约定是因为只有 HTML 元素和 SVG 标签可以以小写字母开头。

function SomeComponent {
  // 代码写在这里
}

你可以定义名称以小写字母开头的函数组件,但在导入时它应该有一个大写字母。这里小写是可以的:

function myComponent {
  render() {
    return <div />;
  }
}

export default myComponent;

而在另一个文件中导入时,它应该以大写字母开头:

import MyComponent from "./myComponent";

⬆ Back to Top

  1. React v16 是否支持自定义 DOM 属性?

注意: 此问题引用的是 React v16,该版本已过时。以下信息适用于 React 16+,包括当前版本(React 18/19)。

是的。从 React 16 开始,React 不再忽略未知的 DOM 属性。如果你在 JSX 中编写了一个 React 无法识别的属性,React 会将其传递到 DOM 中。

例如,让我们看一下下面的属性:

<div mycustomattribute={"something"} />

在 React 15 及更早版本中,这将渲染一个空的 div:

<div />

在 React 16 及更高版本(包括 React 18/19)中,任何未知的属性都将出现在 DOM 中:

<div mycustomattribute="something" />

这对于提供特定于浏览器的非标准属性、尝试新的 DOM API 以及与有特定要求的第三方库集成非常有用。

⬆ Back to Top

  1. 如何在 JSX 中进行循环?

你可以简单地使用 Array.prototype.map 和 ES6箭头函数语法。

例如,将对象数组 items 映射为组件数组:

<tbody>
  {items.map((item) => (
    <SomeComponent key={item.id} name={item.name} />
  ))}
</tbody>

但是你不能使用 for 循环:

<tbody>
  for (let i = 0; i < items.length; i++) {
    <SomeComponent key={items[i].id} name={items[i].name} />
  }
</tbody>

这是因为 JSX 标签会被 transpile 为函数调用,而你不能在表达式内部使用语句。这可能会随着 do 表达式(目前是 stage 1 提案)的出现而改变。

⬆ Back to Top

  1. 如何在属性引号中访问 props?

React(或 JSX)不支持在属性值内部进行变量插值。下面的表示方式将无法工作:

<img className="image" src="images/{this.props.image}" />

但是你可以将任何 JS 表达式放在花括号中作为整个属性值。因此下面的表达式可以工作:

<img className="image" src={"images/" + this.props.image} />

使用模板字符串也可以:

<img className="image" src={`images/${this.props.image}`} />

⬆ Back to Top

  1. 什么是 React proptype array with shape?

如果你想将一个具有特定形状的对象数组传递给组件,那么可以将 React.PropTypes.shape() 作为参数传递给 React.PropTypes.arrayOf()

ReactComponent.propTypes = {
  arrayWithShape: React.PropTypes.arrayOf(
    React.PropTypes.shape({
      color: React.PropTypes.string.isRequired,
      fontSize: React.PropTypes.number.isRequired,
    })
  ).isRequired,
};

⬆ Back to Top

  1. 如何有条件地应用 class 属性?

你不应该在引号内使用花括号,因为它会被计算为字符串。

<div className="btn-panel {this.props.visible ? 'show' : 'hidden'}">

相反,你需要将花括号移到外面(不要忘记在类名之间包含空格):

<div className={'btn-panel ' + (this.props.visible ? 'show' : 'hidden')}>

模板字符串也可以:

<div className={`btn-panel ${this.props.visible ? 'show' : 'hidden'}`}>

⬆ Back to Top

  1. React 和 ReactDOM 有什么区别?

react 包包含 React.createElement()React.ComponentReact.Children 以及其他与元素和组件类相关的辅助函数。你可以将这些视为构建组件所需的同构或通用辅助工具。react-dom 包包含 ReactDOM.render(),而在 react-dom/server 中,我们有 ReactDOMServer.renderToString()ReactDOMServer.renderToStaticMarkup() 支持服务器端渲染

⬆ Back to Top

  1. 为什么 ReactDOM 与 React 分离?

React 团队致力于将所有与 DOM 相关的功能提取到一个名为ReactDOM的单独库中。React v0.14 是第一个拆分这些库的版本。通过查看一些包,如 react-nativereact-artreact-canvasreact-three,我们可以清楚地看到 React 的美妙和本质与浏览器或 DOM 无关。

为了构建更多 React 可以渲染到的环境,React 团队计划将主要的 React 包拆分为两个:reactreact-dom。这为编写可在 React Web 版本和 React Native 之间共享的组件铺平了道路。

⬆ Back to Top

  1. 如何使用 React 的 label 元素?

如果你尝试使用标准的 for 属性渲染一个绑定到文本输入框的 <label> 元素,那么生成的 HTML 会缺少该属性,并向控制台打印一个警告。

<label for={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />

由于 for 是 JavaScript 中的保留关键字,因此请改用 htmlFor

<label htmlFor={'user'}>{'User'}</label>
<input type={'text'} id={'user'} />

⬆ Back to Top

  1. 如何组合多个内联样式对象?

在常规 React 中,你可以使用展开运算符

<button style={{ ...styles.panel.button, ...styles.panel.submitButton }}>
  {"Submit"}
</button>

如果你使用 React Native,那么可以使用数组表示法:

<button style={[styles.panel.button, styles.panel.submitButton]}>
  {"Submit"}
</button>

⬆ Back to Top

  1. 如何在浏览器调整大小时重新渲染视图?

你可以使用 useState hook 来管理宽度和高度状态变量,并使用 useEffect hook 来添加和移除 resize 事件监听器。传递给 useEffect 的 [] 依赖数组确保该 effect 只运行一次(在挂载时),而不是在每次重新渲染时都运行。

import React, { useState, useEffect } from "react";
function WindowDimensions() {
  const [dimensions, setDimensions] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    function handleResize() {
      setDimensions({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return (
    <span>
      {dimensions.width} x {dimensions.height}
    </span>
  );
}

使用类组件

你可以在 componentDidMount() 中监听 resize 事件,然后更新尺寸(widthheight)。你应该在 componentWillUnmount() 方法中移除监听器。

class WindowDimensions extends React.Component {
  constructor(props) {
    super(props);
    this.updateDimensions = this.updateDimensions.bind(this);
  }

  componentWillMount() {
    this.updateDimensions();
  }

  componentDidMount() {
    window.addEventListener("resize", this.updateDimensions);
  }

  componentWillUnmount() {
    window.removeEventListener("resize", this.updateDimensions);
  }

  updateDimensions() {
    this.setState({
      width: window.innerWidth,
      height: window.innerHeight,
    });
  }

  render() {
    return (
      <span>
        {this.state.width} x {this.state.height}
      </span>
    );
  }
}
**[⬆ Back to Top](#table-of-contents)**
  1. 如何在 React 中格式化打印 JSON?

我们可以使用 <pre> 标签,这样 JSON.stringify() 的格式就会被保留:

const data = { name: "John", age: 42 };

function User {
    return <pre>{JSON.stringify(data, null, 2)}</pre>;
}

const container = createRoot(document.getElementById("container"));

container.render(<User />);
  <details><summary><b>类组件示例</b></summary>
  <p>

```jsx harmony
const data = { name: "John", age: 42 };

class User extends React.Component {
  render() {
    return <pre>{JSON.stringify(data, null, 2)}</pre>;
  }
}

React.render(<User />, document.getElementById("container"));
```

  </p>
  </details>

⬆ Back to Top

  1. 为什么不能在 React 中更新 props?

React 的设计理念是 props 应该是不可变的(只读)并且是自顶向下的。这意味着父组件可以向子组件发送任何 prop 值,但子组件不能修改接收到的 props。

⬆ Back to Top

  1. 如何在页面加载时聚焦输入元素?

    对于函数组件,你需要使用 useEffect 钩子在页面加载时设置输入字段的焦点。

    import React, { useEffect, useRef } from "react";
    
    const App = () => {
      const inputElRef = useRef(null);
    
      useEffect(() => {
        inputElRef.current.focus();
      }, []);
    
      return (
        <div>
          <input defaultValue={"不会聚焦"} />
          <input ref={inputElRef} defaultValue={"会聚焦"} />
        </div>
      );
    };
    
    ReactDOM.render(<App />, document.getElementById("app"));
    
    查看类组件方式

    你可以通过为 `input` 元素创建 _ref_ 并在 `componentDidMount()` 中使用它来实现:

    class App extends React.Component {
      componentDidMount() {
        this.nameInput.focus();
      }
    
      render() {
        return (
          <div>
            <input defaultValue={"不会聚焦"} />
            <input
              ref={(input) => (this.nameInput = input)}
              defaultValue={"会聚焦"}
            />
          </div>
        );
      }
    }
    
    ReactDOM.render(<App />, document.getElementById("app"));
    

⬆ 返回顶部

  1. 如何在浏览器运行时查找 React 的版本?

    你可以使用 React.version 来获取版本。

    const REACT_VERSION = React.version;
    
    ReactDOM.render(
      <div>{`React 版本: ${REACT_VERSION}`}</div>,
      document.getElementById("app")
    );
    

⬆ 返回顶部

  1. 如何为 React Router 添加 Google Analytics?

    history 对象上添加一个监听器来记录每个页面浏览:

    history.listen(function (location) {
      window.ga("set", "page", location.pathname + location.search);
      window.ga("send", "pageview", location.pathname + location.search);
    });
    

⬆ 返回顶部

  1. 如何在 React 的内联样式中添加浏览器厂商前缀?

    React 不会 自动应用 浏览器厂商前缀。你需要手动添加厂商前缀。

    <div
      style={{
        transform: "rotate(90deg)",
        WebkitTransform: "rotate(90deg)", // 注意这里的大写 'W'
        msTransform: "rotate(90deg)", // 'ms' 是唯一小写的厂商前缀
      }}
    />
    

⬆ 返回顶部

  1. 如何使用 React 和 ES6 导入和导出组件?

    你应该使用默认导出组件

    import User from "user";
    
    export default function MyProfile {
        return <User type="customer">//...</User>;
    }
    
    查看类组件方式

    ```jsx harmony import React from "react"; import User from "user";

    export default class MyProfile extends React.Component { render() { return //...; } }

    </p>
    </details>
    
    使用导出说明符时,MyProfile 将成为模块的成员并导出,在其他组件中导入时无需指定名称。
    

⬆ 返回顶部

  1. React 组件命名有哪些例外情况?

    组件名称应以大写字母开头,但此约定有一些例外。带有点(属性访问器)的小写标签名仍被视为有效的组件名称。 例如,下面的标签可以编译为有效的组件:

         render() {
              return (
                <obj.component/> // `React.createElement(obj.component)`
              )
        }
    

    ⬆ 返回顶部

  2. 是否可以在纯 React 中使用 async/await?

    是的,你可以在纯 React 中使用 async/await,只要你的 JavaScript 环境支持 ES2017+。如今大多数现代浏览器和构建工具都支持 ES2017+ 版本。如果你使用 Create React AppNext.jsRemix 或任何现代 React 配置,async/await 通过 Babel 开箱即用地支持。

    使用示例

    import { useEffect, useState } from 'react';
    
    function UserProfile() {
      const [user, setUser] = useState(null);
    
      useEffect(() => {
        const fetchUser = async () => {
          const response = await fetch('/api/user');
          const data = await response.json();
          setUser(data);
        };
    
        fetchUser();
      }, []);
    
      return user ? <div>你好,{user.name}</div> : <div>加载中...</div>;
    }
    

    但是,如果你不使用像 Webpack 或 Babel 这样的打包工具,你将需要 Babeltransform-async-to-generator 插件。不过,React Native 附带了 Babel 和一组转换工具。

⬆ 返回顶部

  1. React 常见的文件夹结构有哪些?

    React 项目文件结构有两种常见做法。

    1. 按功能或路由分组:

    一种常见的项目结构方式是将 CSS、JS 和测试文件放在一起,按功能或路由分组。

    common/
    ├─ Avatar.js
    ├─ Avatar.css
    ├─ APIUtils.js
    └─ APIUtils.test.js
    feed/
    ├─ index.js
    ├─ Feed.js
    ├─ Feed.css
    ├─ FeedStory.js
    ├─ FeedStory.test.js
    └─ FeedAPI.js
    profile/
    ├─ index.js
    ├─ Profile.js
    ├─ ProfileHeader.js
    ├─ ProfileHeader.css
    └─ ProfileAPI.js
    
    1. 按文件类型分组:

    另一种流行的项目结构方式是将相似的文件分组在一起。

    api/
    ├─ APIUtils.js
    ├─ APIUtils.test.js
    ├─ ProfileAPI.js
    └─ UserAPI.js
    components/
    ├─ Avatar.js
    ├─ Avatar.css
    ├─ Feed.js
    ├─ Feed.css
    ├─ FeedStory.js
    ├─ FeedStory.test.js
    ├─ Profile.js
    ├─ ProfileHeader.js
    └─ ProfileHeader.css
    

⬆ 返回顶部

  1. 有哪些流行的动画包?

    React Transition GroupReact Motion 是 React 生态系统中流行的动画包。

⬆ 返回顶部

  1. 样式模块的好处是什么?

    建议避免在组件中硬编码样式值。任何可能在不同 UI 组件中使用的值都应提取到它们自己的模块中。

    例如,这些样式可以提取到一个单独的组件中:

    export const colors = {
      white,
      black,
      blue,
    };
    
    export const space = [0, 8, 16, 32, 64];
    

    然后在其他组件中单独导入:

    import { space, colors } from "./styles";
    

⬆ 返回顶部

  1. 有哪些流行的 React 专用代码检查工具?

    ESLint 是一个流行的 JavaScript 代码检查工具。有一些可用的插件可以分析特定的代码风格。React 中最常见的一种是名为 eslint-plugin-react 的 npm 包。默认情况下,它会检查许多最佳实践,规则涵盖从迭代器中的键到完整的 prop 类型集等内容。

    另一个流行的插件是 eslint-plugin-jsx-a11y,它将帮助修复可访问性方面的常见问题。由于 JSX 提供的语法与常规 HTML 略有不同,例如 alt 文本和 tabindex 的问题,常规插件将无法检测到。

⬆ 返回顶部

React Router

⬆ 返回顶部

  1. 什么是 React Router?

    React Router 是一个构建在 React 之上的强大路由库,它能帮助你极其快速地为应用添加新的屏幕和流程,同时保持 URL 与页面上显示的内容同步。

⬆ 返回顶部

  1. React Router 与 history 库有何不同?

    React Router 是 history 库的一个封装,它通过其 browser 和 hash histories 处理与浏览器 window.history 的交互。它还提供了 memory history,这在没有全局 history 的环境中非常有用,例如移动应用开发(React Native)和使用 Node 进行的单元测试。

⬆ 返回顶部

  1. React Router v6 的 <Router> 组件有哪些?

    React Router v6 提供以下 4 个 <Router> 组件:

    1. <BrowserRouter>:对标准 Web 应用使用 HTML5 history API。
    2. <HashRouter>:对静态服务器使用基于哈希的路由。
    3. <MemoryRouter>:对测试和非浏览器环境使用内存中的路由。
    4. <StaticRouter>:为服务器端渲染(SSR)提供静态路由。

    上述组件将创建 browserhashmemorystatic history 实例。React Router v6 通过 router 对象的上下文,使与你的路由器关联的 history 实例的属性和方法可用。

⬆ 返回顶部

  1. historypush()replace() 方法的作用是什么?

    history 实例有两个用于导航的方法。

    1. push()
    2. replace()

    如果你把 history 看作是一个已访问位置的数组,push() 会向数组添加一个新位置,而 replace() 会用新位置替换数组中的当前位置。

⬆ 返回顶部

  1. 如何在 React Router v4 中进行编程式导航?

    有三种不同的方法可以在组件内实现编程式路由/导航。

    1. 使用 withRouter() 高阶函数:

      withRouter() 高阶函数会将 history 对象作为组件的 prop 注入。该对象提供 push()replace() 方法,以避免使用 context。

      import { withRouter } from "react-router-dom"; // 这也适用于 'react-router-native'
      
      const Button = withRouter(({ history }) => (
        <button
          type="button"
          onClick={() => {
            history.push("/new-location");
          }}
        >
          {"点击我!"}
        </button>
      ));
      
    2. 使用 <Route> 组件和 render props 模式:

      <Route> 组件传递与 withRouter() 相同的 props,因此你可以通过 history prop 访问 history 方法。

      import { Route } from "react-router-dom";
      
      const Button = () => (
        <Route
          render={({ history }) => (
            <button
              type="button"
              onClick={() => {
                history.push("/new-location");
              }}
            >
              {"点击我!"}
            </button>
          )}
        />
      );
      
    3. 使用 context:

      此选项不推荐,且被视为不稳定的 API。

      const Button = (props, context) => (
        <button
          type="button"
          onClick={() => {
            context.history.push("/new-location");
          }}
        >
          {"点击我!"}
        </button>
      );
      
      Button.contextTypes = {
        history: React.PropTypes.shape({
          push: React.PropTypes.func.isRequired,
        }),
      };
      

⬆ 返回顶部

  1. 如何在 React Router v4 中获取查询参数?

    React Router v4 中移除了解析查询字符串的功能,因为多年来一直有用户请求支持不同的实现方式。因此,用户可以自行选择喜欢的实现方式。推荐的方法是使用 query strings 库。

    const queryString = require("query-string");
    const parsed = queryString.parse(props.location.search);
    

    如果你想要原生的方法,也可以使用 URLSearchParams

    const params = new URLSearchParams(props.location.search);
    const foo = params.get("name");
    

    你应该为 IE11 使用一个 polyfill

⬆ 返回顶部

  1. 为什么会收到“Router may have only one child element”警告?

    你必须将你的 Route 包裹在 <Switch> 块中,因为 <Switch> 的独特之处在于它只渲染一个匹配的路由。

    首先,你需要将 Switch 添加到导入中:

    import { Switch, Router, Route } from "react-router";
    

    然后在 <Switch> 块中定义路由:

    <Router>
      <Switch>
        <Route {/* ... */} />
        <Route {/* ... */} />
      </Switch>
    </Router>
    

⬆ 返回顶部

  1. 如何在 React Router v4 中向 history.push 方法传递参数?

    导航时,你可以向 history 对象传递 props:

    this.props.history.push({
      pathname: "/template",
      search: "?name=sudheer",
      state: { detail: response.data },
    });
    

    search 属性用于在 push() 方法中传递查询参数。

⬆ 返回顶部

  1. 如何实现 默认 页面或 NotFound 页面?

    <Switch> 会渲染第一个匹配的子 <Route>。没有 path 的 <Route> 总是会匹配。因此,你只需像下面这样去掉 path 属性即可

    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/user" component={User} />
      <Route component={NotFound} />
    </Switch>
    

⬆ 返回顶部

  1. 如何在 React Router v4 中获取 history?

    以下是在 React Router v4 中获取 history 对象的步骤列表,

    1. 创建一个导出 history 对象的模块,并在整个项目中导入此模块。

      例如,创建 history.js 文件:

      import { createBrowserHistory } from "history";
      
      export default createBrowserHistory({
        /* 如果需要,在此处传递配置对象 */
      });
      
    2. 你应该使用 <Router> 组件而不是内置的路由器。在 index.js 文件中导入上面的 history.js

      import { Router } from "react-router-dom";
      import history from "./history";
      import App from "./App";
      
      ReactDOM.render(
        <Router history={history}>
          <App />
        </Router>,
        holder
      );
      
    3. 你也可以使用 history 对象的 push 方法,类似于内置的 history 对象:

      // some-other-file.js
      import history from "./history";
      
      history.push("/go-here");
      

⬆ 返回顶部

  1. 如何在登录后执行自动重定向?

    react-router 包在 React Router 中提供了 <Redirect> 组件。渲染 <Redirect> 将导航到新位置。与服务器端重定向类似,新位置将覆盖历史堆栈中的当前位置。

    import { Redirect } from "react-router";
    
    export default function Login {
        if (this.state.isLoggedIn === true) {
          return <Redirect to="/your/redirect/page" />;
        } else {
          return <div>{"请登录"}</div>;
        }
    }
    
    查看类组件

    import React, { Component } from "react";
    import { Redirect } from "react-router";
    
    export default class LoginComponent extends Component {
      render() {
        if (this.state.isLoggedIn === true) {
          return <Redirect to="/your/redirect/page" />;
        } else {
          return <div>{"请登录"}</div>;
        }
      }
    }
    

⬆ 返回顶部

React 国际化

  1. 什么是 React Intl?

    React Intl 库通过现成的组件和 API,让 React 中的国际化变得简单直观,能够处理从字符串、日期、数字格式化到复数形式等各种需求。React Intl 是 FormatJS 的一部分,它通过自身的组件和 API 提供与 React 的绑定。

⬆ 返回顶部

  1. React Intl 的主要特性是什么?

    React Intl 的主要特性如下:

    1. 显示带分隔符的数字。
    2. 正确显示日期和时间。
    3. 显示相对于“现在”的日期。
    4. 对字符串中的标签进行复数化处理。
    5. 支持 150 多种语言。
    6. 可在浏览器和 Node 环境中运行。
    7. 基于标准构建。

⬆ 返回顶部

  1. React Intl 有哪两种格式化方式?

    该库提供了两种格式化字符串、数字和日期的方式:

    1. 使用 React 组件:

      <FormattedMessage
        id={"account"}
        defaultMessage={"The amount is less than minimum balance."}
      />
      
    2. 使用 API:

      const messages = defineMessages({
        accountMessage: {
          id: "account",
          defaultMessage: "The amount is less than minimum balance.",
        },
      });
      
      formatMessage(messages.accountMessage);
      

⬆ 返回顶部

  1. 如何在 React Intl 中使用 <FormattedMessage> 作为占位符?

    react-intl 中的 <Formatted... /> 组件返回的是元素,而非纯文本,因此不能直接用于占位符、替代文本等场景。这种情况下,你应该使用更底层的 API formatMessage()。你可以通过 injectIntl() 高阶组件将 intl 对象注入到组件中,然后使用该对象上的 formatMessage() 方法来格式化消息。

    import React from "react";
    import { injectIntl, intlShape } from "react-intl";
    
    const MyComponent = ({ intl }) => {
      const placeholder = intl.formatMessage({ id: "messageId" });
      return <input placeholder={placeholder} />;
    };
    
    MyComponent.propTypes = {
      intl: intlShape.isRequired,
    };
    
    export default injectIntl(MyComponent);
    

⬆ 返回顶部

  1. 如何使用 React Intl 获取当前语言环境?

    你可以在应用的任何组件中使用 injectIntl() 来获取当前语言环境:

    import { injectIntl, intlShape } from "react-intl";
    
    const MyComponent = ({ intl }) => (
      <div>{`当前语言环境是 ${intl.locale}`}</div>
    );
    
    MyComponent.propTypes = {
      intl: intlShape.isRequired,
    };
    
    export default injectIntl(MyComponent);
    

⬆ 返回顶部

  1. 如何使用 React Intl 格式化日期?

    injectIntl() 高阶组件会让你通过组件的 props 访问到 formatDate() 方法。FormattedDate 实例内部会使用此方法,它返回格式化日期的字符串表示。

    import { injectIntl, intlShape } from "react-intl";
    
    const stringDate = this.props.intl.formatDate(date, {
      year: "numeric",
      month: "numeric",
      day: "numeric",
    });
    
    const MyComponent = ({ intl }) => (
      <div>{`格式化后的日期是 ${stringDate}`}</div>
    );
    
    MyComponent.propTypes = {
      intl: intlShape.isRequired,
    };
    
    export default injectIntl(MyComponent);
    

    ⬆ 返回顶部

React 测试

  1. React 测试中的浅层渲染器(Shallow Renderer)是什么?

    浅层渲染(Shallow rendering) 在 React 中编写单元测试用例时非常有用。它允许你 只渲染组件的一层深度,并断言其 render 方法返回内容的相关事实,而无需担心子组件的行为——子组件不会被实例化或渲染。

    例如,如果你有以下组件:

    function MyComponent() {
      return (
        <div>
          <span className={"heading"}>{"Title"}</span>
          <span className={"description"}>{"Description"}</span>
        </div>
      );
    }
    

    那么你可以按如下方式进行断言:

    import ShallowRenderer from "react-test-renderer/shallow";
    
    // 在你的测试中
    const renderer = new ShallowRenderer();
    renderer.render(<MyComponent />);
    
    const result = renderer.getRenderOutput();
    
    expect(result.type).toBe("div");
    expect(result.props.children).toEqual([
      <span className={"heading"}>{"Title"}</span>,
      <span className={"description"}>{"Description"}</span>,
    ]);
    

⬆ Back to Top

  1. React 中的 TestRenderer 包是什么?

    该包提供了一个渲染器,可用于将组件渲染为纯 JavaScript 对象,而不依赖于 DOM 或原生移动环境。这个包可以轻松获取由 ReactDOM 或 React Native 渲染的平台视图层次结构(类似于 DOM 树)的快照,而无需使用浏览器或 jsdom

    import TestRenderer from "react-test-renderer";
    
    const Link = ({ page, children }) => <a href={page}>{children}</a>;
    
    const testRenderer = TestRenderer.create(
      <Link page={"https://www.facebook.com/"}>{"Facebook"}</Link>
    );
    
    console.log(testRenderer.toJSON());
    // {
    //   type: 'a',
    //   props: { href: 'https://www.facebook.com/' },
    //   children: [ 'Facebook' ]
    // }
    

⬆ Back to Top

  1. ReactTestUtils 包的用途是什么?

    ReactTestUtils 包含在 with-addons 包中,允许你针对模拟的 DOM 执行操作,以达到单元测试的目的。

⬆ Back to Top

  1. 什么是 Jest?

    Jest 是 Facebook 基于 Jasmine 创建的 JavaScript 单元测试框架,它提供了自动化的模拟创建和 jsdom 环境。它常被用于测试组件。

⬆ Back to Top

  1. Jest 相比 Jasmine 有哪些优势?

    与 Jasmine 相比,Jest 有几个优势:

    • 自动查找源代码中要执行的测试。
    • 运行测试时自动模拟依赖项。
    • 允许你同步测试异步代码。
    • 通过 jsdom 提供假的 DOM 实现来运行测试,因此你的测试可以在命令行中执行。
    • 并行运行测试进程,以便测试更快完成。

⬆ Back to Top

  1. 给出一个简单的 Jest 测试用例示例

    让我们为 sum.js 文件中一个加法函数编写测试:

    const sum = (a, b) => a + b;
    
    export default sum;
    

    创建一个名为 sum.test.js 的文件,其中包含实际测试:

    import sum from "./sum";
    
    test("adds 1 + 2 to equal 3", () => {
      expect(sum(1, 2)).toBe(3);
    });
    

    然后在你的 package.json 中添加以下部分:

    {
      "scripts": {
        "test": "jest"
      }
    }
    

    最后,运行 yarn testnpm test,Jest 将输出结果:

    $ yarn test
    PASS ./sum.test.js
    ✓ adds 1 + 2 to equal 3 (2ms)
    

⬆ Back to Top

React Redux

⬆ 返回顶部

  1. 什么是 flux?

    Flux 是 Facebook 设计的一种应用架构(并非框架或库),用于管理 React 应用中的数据流。它作为传统MVC(模型-视图-控制器) 模式的替代方案而创建,强调单向数据流,使状态变化更可预测且更易于调试。

    Flux 通过组织数据在应用中的流动方式来补充 React,尤其适用于大型或复杂项目。

    Flux 的核心概念

    Flux 运行时使用四个关键组件,每个组件都有特定的职责:

    • Actions(动作)
      • 描述发生了什么的普通 JavaScript 对象或函数(例如,用户交互或 API 响应)。
      • 示例:{ type: 'ADD_TODO', payload: 'Buy milk' }
    • Dispatcher(调度器)
      • 接收动作并将其分发到相应存储的中央枢纽。
      • 一个 Flux 应用中只有一个调度器
    • Stores(存储)
      • 保存应用状态和业务逻辑。
      • 响应来自调度器的动作并相应地更新自身。
      • 它们触发变更事件,视图可以监听这些事件。
    • Views(视图,即 React 组件)
      • 订阅存储,并在数据变化时重新渲染
      • 它们也可以触发新的动作(例如,在用户输入时)。

    调度器、存储和视图组件之间的工作流程以及各自不同的输入和输出如下:

    flux

⬆ 返回顶部

  1. 什么是 Redux?

    Redux 是一个用于 JavaScript 应用的可预测状态容器,最常与 React 一起使用。它帮助你在单一数据源中管理和集中应用的状态,使调试、测试和维护变得更加容易——尤其是在大型或复杂应用中。Redux 核心是一个很小的库(压缩后约 2.5kB),且没有依赖项。

⬆ 返回顶部

  1. Redux 的核心原则是什么?

    Redux 遵循三个基本原则:

    1. 单一数据源: 整个应用的状态存储在单个 store 中的对象树里。单一状态树使跟踪状态随时间的变化、调试或检查应用变得更加容易。
    const store = createStore(reducer);
    
    1. 状态是只读的: 改变状态的唯一方法是触发一个 action,一个描述发生了什么的对象。这确保视图或网络回调都不会直接写入状态。
    const action = { type: 'INCREMENT' };
    store.dispatch(action);
    
    1. 使用纯函数(Reducers)进行修改: 为了指定 action 如何转换状态树,你需要编写 reducers。Reducers 只是纯函数,它接收先前的状态和一个 action 作为参数,并返回下一个状态。
    function counter(state = 0, action) {
      switch (action.type) {
        case 'INCREMENT':
          return state + 1;
        case 'DECREMENT':
          return state - 1;
        default:
          return state;
      }
    }
    

⬆ 返回顶部

  1. 与 Flux 相比,Redux 有哪些缺点?

    虽然 Redux 提供了强大且可预测的状态管理解决方案,但与 Flux 相比,它也存在一些权衡。这些包括:

    1. 不可变性至关重要
      Redux 对状态更新强制执行严格的不可变模型,这与 Flux 更为宽松的方式不同。这意味着你必须避免直接修改状态。许多与 Redux 相关的库都假设状态是不可变的,因此你的团队在编写纯更新逻辑时必须非常谨慎。你可以使用 redux-immutable-state-invariantImmerImmutable.js 等工具来帮助实施这一实践,尤其是在开发过程中。
    2. 需谨慎选择补充包
      Redux 设计上更精简,并提供了诸如中间件和 store 增强器等扩展点。这催生了庞大的生态系统,但也意味着你必须深思熟虑地选择和配置额外的包来实现撤销/重做、持久化或表单处理等功能——这些是 Flux 明确不包含但在较小规模项目中可能更容易管理的方面。
    3. 静态类型集成有限
      虽然 Flux 通过 Flow 等工具对静态类型检查有成熟的支持,但 Redux 的类型集成相对不够顺畅。尽管 TypeScript 现在常与 Redux 一起使用,但早期对 Flow 的支持有限,且为实现静态类型安全需要更多的样板代码。这可能会影响那些在大型代码库中严重依赖类型系统的团队。

⬆ 返回顶部

  1. mapStateToProps()mapDispatchToProps() 之间有什么区别?

    mapStateToProps() 是一个工具函数,帮助你的组件获取更新后的状态(该状态由其他组件更新):

    const mapStateToProps = (state) => {
      return {
        todos: getVisibleTodos(state.todos, state.visibilityFilter),
      };
    };
    

    mapDispatchToProps() 是一个工具函数,帮助你的组件触发 action 事件(派发可能导致应用状态改变的 action):

    const mapDispatchToProps = (dispatch) => {
      return {
        onTodoClick: (id) => {
          dispatch(toggleTodo(id));
        },
      };
    };
    

    建议始终对 mapDispatchToProps 使用“对象简写”形式。

    Redux 会将其包装在另一个类似 (...args) => dispatch(onTodoClick(...args)) 的函数中,并将这个包装函数作为 prop 传递给你的组件。

    const mapDispatchToProps = {
      onTodoClick,
    };
    

⬆ 返回顶部

  1. 可以在 reducer 中派发 action 吗?

    在 reducer 内部派发 action 是一种反模式。你的 reducer 应该是无副作用的,仅仅消化 action 的 payload 并返回一个新的状态对象。在 reducer 中添加监听器和派发 action 可能会导致链式 action 和其他副作用。

⬆ 返回顶部

  1. 如何在组件外部访问 Redux store?

    你只需要从使用 createStore() 创建 store 的模块中导出 store。此外,不应将其污染到全局 window 对象。

    store = createStore(myReducer);
    
    export default store;
    

⬆ 返回顶部

  1. MVW 模式有哪些缺点?

    1. DOM 操作非常昂贵,这导致应用运行缓慢且效率低下。
    2. 由于循环依赖,在模型和视图周围创建了复杂的模型。
    3. 协作应用(如 Google Docs)中会发生大量数据更改。
    4. 如果不添加大量额外代码,就无法轻松地进行撤销(回到过去)操作。

⬆ 返回顶部

  1. Redux 和 RxJS 之间有什么相似之处吗?

    这些库用途迥异,但存在一些模糊的相似之处。

    Redux 是一个用于管理整个应用状态的工具。它通常用作 UI 的架构。可以把它看作是(Angular 的)一半的替代方案。RxJS 是一个响应式编程库。它通常用作在 JavaScript 中完成异步任务的工具。可以把它看作是 Promises 的替代方案。Redux 使用响应式范式,因为 Store 是响应式的。Store 从远处观察 action,并自行改变。RxJS 也使用响应式范式,但它不是一种架构,而是提供了基本的构建块(Observables)来实现这种模式。

⬆ 返回顶部

  1. 如何在 Redux 中重置状态?

    你需要在应用中编写一个根 reducer,它将 action 的处理委托给由 combineReducers() 生成的 reducer。

    例如,假设我们希望 rootReducer() 在收到 USER_LOGOUT action 后返回初始状态。我们知道,当 reducers 被调用时,如果第一个参数为 undefined,无论 action 是什么,它都应该返回初始状态。

    const appReducer = combineReducers({
      /* 你的应用的顶层 reducers */
    });
    
    const rootReducer = (state, action) => {
      if (action.type === "USER_LOGOUT") {
        state = undefined;
      }
    
      return appReducer(state, action);
    };
    

    如果使用 redux-persist,你可能还需要清理存储。redux-persist 会在存储引擎中保存一份你的状态副本。首先,你需要导入适当的存储引擎,然后在将状态设置为 undefined 之前解析状态,并清理每个存储状态键。

    const appReducer = combineReducers({
      /* 你的应用的顶层 reducers */
    });
    
    const rootReducer = (state, action) => {
      if (action.type === "USER_LOGOUT") {
        Object.keys(state).forEach((key) => {
          storage.removeItem(`persist:${key}`);
        });
    
        state = undefined;
      }
    
      return appReducer(state, action);
    };
    

⬆ 返回顶部

  1. React context 和 React Redux 之间有什么区别?

    你可以直接在应用中使用Context,它非常适合将数据传递给深层嵌套的组件,这也是它设计的初衷。

    Redux 功能更强大,提供了许多 Context API 所不具备的特性。此外,React Redux 内部使用 context,但它不会在公共 API 中暴露这一事实。

⬆ 返回顶部

  1. 为什么 Redux 的状态函数被称为 reducers?

    Reducers 总是返回状态的累积结果(基于所有先前的和当前的 action)。因此,它们起到了状态“减速器”的作用。每次调用 Redux reducer 时,都会将 state 和 action 作为参数传递。然后,这个 state 会根据 action 被“缩减”(或累积),并返回下一个 state。你可以对一系列 action 和一个初始的 store 状态执行“缩减”操作,以得到最终的状态。

⬆ 返回顶部

  1. 如何在 Redux 中进行 AJAX 请求?

    你可以使用 redux-thunk 中间件,它允许你定义异步 action。

    让我们以使用 fetch API 获取特定账户信息的 AJAX 调用为例:

    export function fetchAccount(id) {
      return (dispatch) => {
        dispatch(setLoadingAccountState()); // 显示加载指示器
        fetch(`/account/${id}`, (response) => {
          dispatch(doneFetchingAccount()); // 隐藏加载指示器
          if (response.status === 200) {
            dispatch(setAccount(response.json)); // 使用普通函数设置接收到的状态
          } else {
            dispatch(someError);
          }
        });
      };
    }
    
    function setAccount(data) {
      return { type: "SET_Account", data: data };
    }
    

⬆ 返回顶部

  1. 我应该将所有组件的状态都保存在 Redux store 中吗?

    将数据保存在 Redux store 中,而将与 UI 相关的状态保留在组件内部。

⬆ 返回顶部

  1. 访问 Redux store 的正确方式是什么?

    在组件中访问 store 的最佳方式是使用 connect() 函数,它会创建一个新的组件来包装你现有的组件。这种模式称为高阶组件,通常是在 React 中扩展组件功能的首选方式。这允许你将 state 和 action 创建器映射到组件,并在 store 更新时自动将它们传入。

    让我们以使用 connect 的 <FilterLink> 组件为例:

    import { connect } from "react-redux";
    import { setVisibilityFilter } from "../actions";
    import Link from "../components/Link";
    
    const mapStateToProps = (state, ownProps) => ({
      active: ownProps.filter === state.visibilityFilter,
    });
    
    const mapDispatchToProps = (dispatch, ownProps) => ({
      onClick: () => dispatch(setVisibilityFilter(ownProps.filter)),
    });
    
    const FilterLink = connect(mapStateToProps, mapDispatchToProps)(Link);
    
    export default FilterLink;
    

    由于 connect() 有不少性能优化,而且通常不太可能导致 bug,Redux 开发者几乎总是推荐使用 connect(),而不是直接访问 store(使用 context API)。

    function MyComponent {
      someMethod() {
        doSomethingWith(this.context.store);
      }
    }
    

⬆ 返回顶部

  1. React Redux 中的 component 和 container 有什么区别?

    Component(组件) 是描述应用展示部分的类组件或函数组件。

    Container(容器) 是连接到 Redux store 的组件的非正式术语。容器订阅 Redux 状态更新并派发 action,它们通常不渲染 DOM 元素;而是将渲染委托给展示性子组件。

⬆ 返回顶部

  1. Redux 中 constants(常量)的作用是什么?

    常量允许你在使用 IDE 时轻松找到项目中特定功能的所有用法。它还可以防止你因拼写错误而引入低级错误——在这种情况下,你会立即得到一个 ReferenceError

    通常我们会将它们保存在单个文件中(constants.jsactionTypes.js)。

    export const ADD_TODO = "ADD_TODO";
    export const DELETE_TODO = "DELETE_TODO";
    export const EDIT_TODO = "EDIT_TODO";
    export const COMPLETE_TODO = "COMPLETE_TODO";
    export const COMPLETE_ALL = "COMPLETE_ALL";
    export const CLEAR_COMPLETED = "CLEAR_COMPLETED";
    

    在 Redux 中,你会在两个地方使用它们:

    1. 创建 action 时:

      actions.js 为例:

      import { ADD_TODO } from "./actionTypes";
      
      export function addTodo(text) {
        return { type: ADD_TODO, text };
      }
      
    2. 在 reducers 中:

      创建 reducer.js

      import { ADD_TODO } from "./actionTypes";
      
      export default (state = [], action) => {
        switch (action.type) {
          case ADD_TODO:
            return [
              ...state,
              {
                text: action.text,
                completed: false,
              },
            ];
          default:
            return state;
        }
      };
      

⬆ 返回顶部

  1. 编写 mapDispatchToProps() 的不同方式有哪些?

    有几种方法可以将action creators 绑定到 mapDispatchToProps() 中的 dispatch()

    以下是可能的选项:

    const mapDispatchToProps = (dispatch) => ({
      action: () => dispatch(action()),
    });
    
    const mapDispatchToProps = (dispatch) => ({
      action: bindActionCreators(action, dispatch),
    });
    
    const mapDispatchToProps = { action };
    

    第三种选项只是第一种的简写形式。

⬆ 返回顶部

  1. mapStateToProps()mapDispatchToProps() 中的 ownProps 参数有什么用?

    如果指定了 ownProps 参数,React Redux 会将传递给组件的 props 传入你的 connect 函数。因此,如果你使用一个已连接的组件:

    import ConnectedComponent from "./containers/ConnectedComponent";
    
    <ConnectedComponent user={"john"} />;
    

    你的 mapStateToProps()mapDispatchToProps() 函数内部的 ownProps 将是一个对象:

    {
      user: "john";
    }
    

    你可以使用这个对象来决定从这些函数返回什么。

⬆ 返回顶部

  1. Redux 顶级目录应该如何组织?

    大多数应用程序有以下几个顶级目录:

    1. Components(组件):用于无状态组件,它们不感知 Redux。
    2. Containers(容器):用于有状态组件,它们连接到 Redux。
    3. Actions(动作):用于所有 action 创建器,文件名对应应用的某个部分。
    4. Reducers(减速器):用于所有 reducers,文件名对应状态的键。
    5. Store:用于 store 的初始化。

    这种结构适用于中小型应用。

⬆ 返回顶部

  1. 什么是 redux-saga?

    redux-saga 是一个旨在使 React/Redux 应用中的副作用(如数据获取等异步操作以及访问浏览器缓存等非纯操作)变得更容易处理和更好管理的库。

    它可以通过 NPM 安装:

    $ npm install --save redux-saga
    

⬆ 返回顶部

  1. redux-saga 的核心思想是什么?

    Saga(传奇) 就像应用中的一个单独线程,专门负责处理副作用。redux-saga 是一个 redux 中间件,这意味着这个线程可以通过正常的 Redux action 从主应用程序启动、暂停和取消,它可以访问完整的 Redux 应用状态,也可以派发 Redux action。

⬆ 返回顶部

  1. redux-saga 中的 call()put() 有什么区别?

    call()put() 都是 effect 创建函数。call() 函数用于创建 effect 描述,指示中间件调用 promise。put() 函数创建一个 effect,指示中间件向 store 派发一个 action。

    让我们以获取特定用户数据为例,看看这些 effects 是如何工作的。

    function* fetchUserSaga(action) {
      // `call` 函数接受剩余参数,这些参数将被传递给 `api.fetchUser` 函数。
      // 指示中间件调用 promise,其解析后的值将被赋给 `userData` 变量
      const userData = yield call(api.fetchUser, action.userId);
    
      // 指示中间件派发相应的 action。
      yield put({
        type: "FETCH_USER_SUCCESS",
        userData,
      });
    }
    

⬆ 返回顶部

  1. 什么是 Redux Thunk?

    Redux Thunk 中间件允许你编写返回函数而不是 action 的 action 创建器。Thunk 可用于延迟 action 的派发,或者仅在满足特定条件时才派发。这个内部函数接收 store 的 dispatch()getState() 方法作为参数。

⬆ 返回顶部

  1. redux-sagaredux-thunk 有什么区别?

    Redux ThunkRedux Saga 都用于处理副作用。在大多数情况下,Thunk 使用 Promise 来处理副作用,而 Saga 则使用 Generator。Thunk 易于使用,且 Promise 为许多开发者所熟悉;Saga/Generator 功能更强大,但需要学习相关知识。不过这两种中间件可以共存,因此你可以先使用 Thunk,在需要时再引入 Saga。

⬆ Back to Top

  1. 什么是 Redux DevTools?

    Redux DevTools 是一个用于 Redux 的实时编辑时间旅行环境,具有热重载、操作重放和可自定义 UI 等功能。如果你不想费心安装 Redux DevTools 并将其集成到项目中,可以考虑使用适用于 Chrome 和 Firefox 的 Redux DevTools 扩展。

⬆ Back to Top

  1. Redux DevTools 有哪些功能?

    Redux DevTools 的主要功能如下:

    1. 允许你检查每个状态和操作的负载。
    2. 允许你通过 取消 操作回到过去。
    3. 如果你更改了 reducer 代码,每个 暂存 的操作都将被重新计算。
    4. 如果 reducer 抛出错误,你将看到错误发生在哪个操作期间以及具体错误信息。
    5. 使用 persistState() 存储增强器,你可以在页面重新加载时保留调试会话。

⬆ Back to Top

  1. 什么是 Redux 选择器(selectors)?为什么要使用它们?

    选择器 是将 Redux 状态作为参数并返回要传递给组件的数据的函数。

    例如,从状态中获取用户详细信息:

    const getUserData = (state) => state.user.data;
    

    这些选择器有两个主要好处:

    1. 选择器可以计算派生数据,使 Redux 能够存储尽可能少的状态。
    2. 除非其参数之一发生变化,否则选择器不会重新计算。

⬆ Back to Top

  1. 什么是 Redux Form?

    Redux Form 与 React 和 Redux 配合使用,使 React 中的表单能够使用 Redux 存储其所有状态。Redux Form 可以与原始 HTML5 输入一起使用,但它也能很好地与 Material UI、React Widgets 和 React Bootstrap 等常见 UI 框架配合。

⬆ Back to Top

  1. Redux Form 的主要功能是什么?

    Redux Form 的主要功能包括:

    1. 通过 Redux 存储实现字段值持久化。
    2. 验证(同步/异步)和提交。
    3. 字段值的格式化、解析和规范化。

⬆ Back to Top

  1. 如何向 Redux 添加多个中间件?

    你可以使用 applyMiddleware()

    例如,你可以将 redux-thunklogger 作为参数传递给 applyMiddleware()

    import { createStore, applyMiddleware } from "redux";
    const createStoreWithMiddleware = applyMiddleware(
      ReduxThunk,
      logger
    )(createStore);
    

⬆ Back to Top

  1. 如何在 Redux 中设置初始状态?

    你需要将初始状态作为第二个参数传递给 createStore:

    const rootReducer = combineReducers({
      todos: todos,
      visibilityFilter: visibilityFilter,
    });
    
    const initialState = {
      todos: [{ id: 123, name: "example", completed: false }],
    };
    
    const store = createStore(rootReducer, initialState);
    

⬆ Back to Top

  1. Relay 与 Redux 有何不同?

    Relay 与 Redux 的相似之处在于它们都使用单一存储。主要区别在于,Relay 仅管理源自服务器的状态,并且所有对状态的访问都通过 GraphQL 查询(用于读取数据)和变更(用于更改数据)进行。Relay 会为你缓存数据并优化数据获取,只获取已更改的数据,不多获取其他内容。

  2. Redux 中的 action 是什么?

    Action 是普通的 JavaScript 对象或信息载体,用于将数据从应用程序发送到存储。它们是存储的唯一信息来源。Action 必须具有一个 type 属性,用于指示正在执行的操作类型。

    例如,以下是一个表示添加新待办事项的 action:

    {
      type: ADD_TODO,
      text: 'Add todo item'
    }
    

⬆ Back to Top

React Native

⬆ 返回顶部

  1. React Native 和 React 有什么区别?

    React 是一个 JavaScript 库,支持前端 Web 开发并可在服务器端运行,用于构建用户界面和 Web 应用程序。

    React Native 是一个移动框架,它会编译为原生应用组件,让你能够使用 JavaScript 构建原生移动应用(iOS、Android 和 Windows),你可以使用 React 来构建组件,并且它在底层实现了 React。

⬆ 返回顶部

  1. 如何测试 React Native 应用?

    React Native 只能在 iOS 和 Android 等移动模拟器中进行测试。你可以使用 Expo 应用(https://expo.io)在移动设备上运行应用,它通过二维码进行同步,此时你的移动设备和电脑需要处于同一无线网络。

⬆ 返回顶部

  1. 如何在 React Native 中进行日志记录?

    你可以使用 console.logconsole.warn 等方法。从 React Native v0.29 开始,你只需运行以下命令即可在控制台中查看日志:

    $ react-native log-ios
    $ react-native log-android
    

⬆ 返回顶部

  1. 如何调试 React Native 应用?

    按照以下步骤调试 React Native 应用:

    1. 在 iOS 模拟器中运行你的应用。
    2. 按下 Command + D,此时会打开一个网页 http://localhost:8081/debugger-ui
    3. 启用“Pause On Caught Exceptions”(在捕获到异常时暂停)以获得更好的调试体验。
    4. 按下 Command + Option + I 打开 Chrome 开发者工具,或者通过“View”(视图)->“Developer”(开发者)->“Developer Tools”(开发者工具)打开。
    5. 现在你应该能够像平常一样进行调试了。

React 支持的库与集成

⬆ 返回顶部

  1. 什么是 reselect?它是如何工作的?

    Reselect 是一个(用于 Redux 的)选择器库,它运用了 记忆化 的概念。它最初是为了从类 Redux 应用的状态中计算派生数据而编写的,但它并不绑定于任何架构或库。

    Reselect 会保留上次调用的输入/输出副本,并且只有当其中一个输入发生变化时才会重新计算结果。如果连续两次提供相同的输入,Reselect 会返回缓存的输出。它的记忆化和缓存是完全可自定义的。

⬆ 返回顶部

  1. 什么是 Flow?

    Flow 是一个 静态类型检查器,旨在发现 JavaScript 中的类型错误。Flow 类型能够表达比传统类型系统更细粒度的区分。例如,与大多数类型系统不同,Flow 可以帮助你捕获涉及 null 的错误。

⬆ 返回顶部

  1. Flow 和 PropTypes 有什么区别?

    Flow 是一个 静态分析工具(静态检查器),它使用该语言的超集,允许你为所有代码添加类型注解,并在编译时捕获一整类错误。

    PropTypes 是一个 基本类型检查器(运行时检查器),它是 React 的附加功能。它只能检查传递给特定组件的 props 的类型。如果你希望为整个项目进行更灵活的类型检查,Flow/TypeScript 是合适的选择。

⬆ 返回顶部

  1. 如何在 React 中使用 Font Awesome 图标?

    按照以下步骤在 React 中引入 Font Awesome:

    1. 安装 font-awesome

      $ npm install --save font-awesome
      
    2. 在你的 index.js 文件中导入 font-awesome

      import "font-awesome/css/font-awesome.min.css";
      
    3. className 中添加 Font Awesome 类:

      function MyComponent {
        return <div><i className={'fa fa-spinner'} /></div>
      }
      

⬆ 返回顶部

  1. 什么是 React Dev Tools?

    React Developer Tools 允许你检查组件层次结构,包括组件的 props 和 state。它既可以作为浏览器扩展(适用于 Chrome 和 Firefox),也可以作为独立应用(适用于其他环境,包括 Safari、IE 和 React Native)。

    官方扩展适用于不同的浏览器或环境:

    1. Chrome 扩展
    2. Firefox 扩展
    3. 独立应用(Safari、React Native 等)

⬆ 返回顶部

  1. 为什么 DevTools 在 Chrome 中无法加载本地文件?

    如果你在浏览器中打开本地 HTML 文件(file://...),则必须先打开“Chrome 扩展程序”并勾选“允许访问文件 URL”。

⬆ 返回顶部

  1. 如何在 React 中使用 Polymer?

    要在 React 中使用 Polymer,需要按照以下步骤操作:

    1. 创建一个 Polymer 元素:

      <link
        rel="import"
        href="../../bower_components/polymer/polymer.html"
      />;
      Polymer({
        is: "calendar-element",
        ready: function () {
          this.textContent = "I am a calendar";
        },
      });
      
    2. 通过在 HTML 文档中导入来创建 Polymer 组件 HTML 标签,例如,在 React 应用的 index.html 中导入:

      <link
        rel="import"
        href="./src/polymer-components/calendar-element.html"
      />
      
    3. 在 JSX 文件中使用该元素:

      export default function MyComponent {
        return <calendar-element />;
      }
      

⬆ 返回顶部

  1. React 相比 Vue.js 有哪些优势?

    React 相比 Vue.js 具有以下优势:

    1. 在大型应用开发中提供更大的灵活性。
    2. 更容易测试。
    3. 适合创建移动应用。
    4. 可获取的信息和解决方案更多。

注意: 以上优势列表纯属个人观点,可能因专业经验而异,但它们可作为基本参考依据。

⬆ 返回顶部

  1. React 和 Angular 有什么区别?

    让我们通过表格形式看看 React 和 Angular 的区别。

    React Angular
    React 是一个库,仅包含视图层 Angular 是一个框架,具有完整的 MVC 功能
    React 处理服务器端渲染 AngularJS 仅在客户端渲染,但 Angular 2 及以上版本支持服务器端渲染
    React 使用 JSX,看起来像 JS 中的 HTML,可能会造成混淆 Angular 采用模板化的 HTML 方法,使代码更简洁易懂
    React Native 是 React 的一种类型,用于构建移动应用,速度更快且更稳定 Ionic 作为 Angular 的移动原生应用,相对不够稳定且速度较慢
    在 React 中,数据仅单向流动,因此调试更容易 在 Angular 中,数据双向流动,即子组件和父组件之间存在双向数据绑定,因此调试往往比较困难

注意: 以上区别列表纯属个人观点,可能因专业经验而异,但它们可作为基本参考依据。

⬆ 返回顶部

  1. 为什么 React 选项卡在 DevTools 中不显示?

    页面加载时,React DevTools 会设置一个名为 __REACT_DEVTOOLS_GLOBAL_HOOK__ 的全局变量,然后 React 在初始化期间会与此钩子进行通信。如果网站未使用 React,或者 React 无法与 DevTools 通信,那么该选项卡就不会显示。

⬆ 返回顶部

  1. 什么是 Styled Components?

    styled-components 是一个用于为 React 应用设置样式的 JavaScript 库。它消除了样式和组件之间的映射关系,让你能够编写用 JavaScript 增强的实际 CSS。

⬆ 返回顶部

  1. 给出一个 Styled Components 的示例?

    让我们创建具有特定样式的 <Title><Wrapper> 组件。

    import React from "react";
    import styled from "styled-components";
    
    // 创建一个 <Title> 组件,它渲染一个 <h1>,居中显示、红色、大小为 1.5em
    const Title = styled.h1`
      font-size: 1.5em;
      text-align: center;
      color: palevioletred;
    `;
    
    // 创建一个 <Wrapper> 组件,它渲染一个 <section>,带有一些内边距和木瓜奶色背景
    const Wrapper = styled.section`
      padding: 4em;
      background: papayawhip;
    `;
    

    现在,TitleWrapper 这两个变量是组件,你可以像渲染其他任何 React 组件一样渲染它们。

    <Wrapper>
      <Title>{"让我们开始使用第一个 styled component!"}</Title>
    </Wrapper>
    

⬆ 返回顶部

  1. 什么是 Relay?

    Relay 是一个 JavaScript 框架,用于为使用 React 视图层的 Web 应用程序提供数据层和客户端 - 服务器通信。

⬆ 返回顶部

其他

  1. Reselect 库的主要特性是什么?

    让我们看看 Reselect 库的主要特性:

    1. 选择器可以计算派生数据,使 Redux 能够存储尽可能精简的状态。
    2. 选择器具有高效性。只有当选择器的某个参数发生变化时,它才会重新计算。
    3. 选择器具有可组合性。它们可以作为其他选择器的输入。
  2. 请给出 Reselect 的使用示例?

    让我们通过 Reselect 的简化用法来计算发货订单的各种金额:

    import { createSelector } from "reselect";
    
    const shopItemsSelector = (state) => state.shop.items;
    const taxPercentSelector = (state) => state.shop.taxPercent;
    
    const subtotalSelector = createSelector(shopItemsSelector, (items) =>
      items.reduce((acc, item) => acc + item.value, 0)
    );
    
    const taxSelector = createSelector(
      subtotalSelector,
      taxPercentSelector,
      (subtotal, taxPercent) => subtotal * (taxPercent / 100)
    );
    
    export const totalSelector = createSelector(
      subtotalSelector,
      taxSelector,
      (subtotal, tax) => ({ total: subtotal + tax })
    );
    
    let exampleState = {
      shop: {
        taxPercent: 8,
        items: [
          { name: "apple", value: 1.2 },
          { name: "orange", value: 0.95 },
        ],
      },
    };
    
    console.log(subtotalSelector(exampleState)); // 2.15
    console.log(taxSelector(exampleState)); // 0.172
    console.log(totalSelector(exampleState)); // { total: 2.322 }
    

⬆ 返回顶部

  1. Redux 是否只能与 React 一起使用?

    Redux 可以用作任何 UI 层的数据存储。最常见的用法是与 React 和 React Native 一起使用,但也有适用于 Angular、Angular 2、Vue、Mithril 等的绑定库。Redux 提供了一种订阅机制,可供任何其他代码使用。

⬆ 返回顶部

  1. 使用 Redux 是否需要特定的构建工具?

    Redux 最初是用 ES6 编写的,并通过 Webpack 和 Babel 转译为生产环境的 ES5。无论您使用何种 JavaScript 构建流程,都应该能够使用它。Redux 还提供了 UMD 构建版本,可以直接使用,完全不需要任何构建流程。

⬆ 返回顶部

  1. Redux Form 的 initialValues 如何从 state 中更新?

    您需要添加 enableReinitialize: true 设置。

    const InitializeFromStateForm = reduxForm({
      form: "initializeFromState",
      enableReinitialize: true,
    })(UserEdit);
    

    如果您的 initialValues 属性得到更新,您的表单也会随之更新。

⬆ 返回顶部

  1. React PropTypes 如何允许一个 prop 接受不同的类型?

    您可以使用 PropTypesoneOfType() 方法。

    例如,height 属性可以定义为 stringnumber 类型,如下所示:

    Component.propTypes = {
      size: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    };
    

⬆ 返回顶部

  1. 我可以将 SVG 文件作为 React 组件导入吗?

    您可以直接将 SVG 作为组件导入,而不是作为文件加载。此功能在 react-scripts@2.0.0 及更高版本中可用。

    import { ReactComponent as Logo } from "./logo.svg";
    
    const App = () => (
      <div>
        {/* Logo 是一个实际的 React 组件 */}
        <Logo />
      </div>
    );
    

    注意:不要忘记 import 语句中的花括号。

⬆ 返回顶部

  1. React 中的渲染劫持(render hijacking)是什么?

    渲染劫持的概念是指能够从另一个组件控制某个组件的输出。这意味着您通过将组件包装到高阶组件(Higher-Order Component)中来装饰它。通过包装,您可以注入额外的 props 或进行其他更改,从而可能改变渲染逻辑。它实际上并不启用“劫持”,而是通过使用 HOC 使您的组件表现出不同的行为。

⬆ 返回顶部

  1. 如何向 React 组件传递数字?

    我们可以使用花括号 {}numbers 作为 props 传递给 React 组件,而 strings 则使用双引号 "" 或单引号 ''

    import React from "react";
    
    const ChildComponent = ({ name, age }) => {
      return (
        <>
          我的名字是 {name},年龄是 {age}
        </>
      );
    };
    
    const ParentComponent = () => {
      return (
        <>
          <ChildComponent name="Chetan" age={30} />
        </>
      );
    };
    
    export default ParentComponent;
    

⬆ 返回顶部

  1. 我需要将所有状态都放入 Redux 吗?我应该使用 React 的内部状态吗?

    这取决于开发者的决定,即确定应用程序由哪些类型的状态组成,以及每部分状态应该存放在哪里是开发者的职责。有些用户喜欢将每一个数据都保存在 Redux 中,以便始终维护应用程序的完全可序列化和可控版本。另一些用户则喜欢将非关键或 UI 状态(例如“此下拉菜单当前是否打开”)保存在组件的内部状态中。

    以下是一些经验法则,用于确定哪些类型的数据应该放入 Redux:

    1. 应用程序的其他部分是否关心此数据?
    2. 您是否需要能够基于此原始数据创建进一步的派生数据?
    3. 相同的数据是否用于驱动多个组件?
    4. 能够将此状态恢复到特定时间点(即时间旅行调试)对您有价值吗?
    5. 您是否想要缓存数据(即,如果状态中已有数据,则使用它而不是重新请求)?

⬆ 返回顶部

  1. React 中 registerServiceWorker 的目的是什么?

    React 默认会为您创建一个无需任何配置的 service worker。Service worker 是一个 Web API,它帮助您缓存资源和其他文件,这样当用户处于离线状态或网络缓慢时,他/她仍然可以在屏幕上看到结果。因此,它帮助您构建更好的用户体验,这是您现在应该了解的关于 service worker 的全部内容。它全部是关于为您的网站添加离线功能。

    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import registerServiceWorker from "./registerServiceWorker";
    
    ReactDOM.render(<App />, document.getElementById("root"));
    registerServiceWorker();
    

⬆ 返回顶部

  1. React memo 函数是什么?

    类组件可以通过 PureComponent 或 shouldComponentUpdate 来限制在输入 props 相同时不重新渲染。现在,您可以通过将函数组件包装在 React.memo 中来实现同样的功能。

    const MyComponent = React.memo(function MyComponent(props) {
      /* 仅在 props 变化时重新渲染 */
    });
    

⬆ 返回顶部

  1. React lazy 函数是什么?

    React.lazy 函数允许您将动态导入渲染为常规组件。当该组件被渲染时,它将自动加载包含 OtherComponent 的 bundle。这必须返回一个 Promise,该 Promise 解析为一个包含 React 组件默认导出的模块。

    const OtherComponent = React.lazy(() => import("./OtherComponent"));
    
    function MyComponent() {
      return (
        <div>
          <OtherComponent />
        </div>
      );
    }
    

    注意: React.lazySuspense 目前尚不支持服务器端渲染。如果您想在服务器渲染的应用中进行代码分割,我们仍然推荐使用 React Loadable。

⬆ 返回顶部

  1. 如何使用 setState 防止不必要的更新?

    您可以将状态的当前值与现有状态值进行比较,并决定是否重新渲染页面。如果值相同,则需要返回 null 以停止重新渲染,否则返回最新的状态值。

    例如,用户个人资料信息的条件渲染如下:

    getUserProfile = (user) => {
      const latestAddress = user.address;
      this.setState((state) => {
        if (state.address === latestAddress) {
          return null;
        } else {
          return { title: latestAddress };
        }
      });
    };
    

⬆ 返回顶部

  1. 如何在 React 16 版本中渲染数组、字符串和数字?

    注意: 此问题引用 React 16。这些特性在当前 React 版本(18/19)中仍然有效。

    数组:从 React 16 开始,您可以通过返回数组或使用 Fragments 来返回多个同级元素,而无需包裹元素。

    数组方式:

    const ReactJSDevs = () => {
      return [
        <li key="1">John</li>,
        <li key="2">Jackie</li>,
        <li key="3">Jordan</li>,
      ];
    };
    

    Fragment 方式(推荐):

    const ReactJSDevs = () => {
      return (
        <>
          <li>John</li>
          <li>Jackie</li>
          <li>Jordan</li>
        </>
      );
    };
    

    您也可以将此项目数组合并到另一个数组组件中。

    const JSDevs = () => {
      return (
        <ul>
          <li>Brad</li>
          <li>Brodge</li>
          <ReactJSDevs />
          <li>Brandon</li>
        </ul>
      );
    };
    

    字符串和数字: 您也可以从 render 方法返回字符串和数字类型。

    render() {
     return '欢迎来到 ReactJS 问题';
    }
    // 数字
    render() {
     return 2018;
    }
    

⬆ 返回顶部

  1. 什么是 Hooks?

    Hooks 是一种特殊的 JavaScript 函数,允许您在不编写类的情况下使用状态和其他 React 特性。此模式作为 React 16.8 中的新功能引入,有助于将有状态逻辑与组件隔离开来。

    让我们看一个 useState Hook 的例子:

    import { useState } from "react";
    
    function Example() {
      // 声明一个新的状态变量,我们将其称为 "count"
      const [count, setCount] = useState(0);
    
      return (
        <>
          <p>您点击了 {count} 次</p>
          <button onClick={() => setCount(count + 1)}>点击我</button>
        </>
      );
    }
    

    注意: Hooks 可以在现有的函数组件内部使用,而无需重写组件。

⬆ 返回顶部

  1. 使用 Hooks 需要遵循哪些规则?

    为了使用 Hooks,您需要遵循两条规则:

    1. 只在 React 函数的顶层调用 Hooks:您应该始终在 React 函数的顶层使用 Hooks,在任何提前返回之前。即,您不应该在循环、条件或嵌套函数中调用 Hooks。这将确保 Hooks 在每次组件渲染时都以相同的顺序被调用,并且由于 useStateuseEffect 的调用,Hooks 的状态在多次重新渲染之间得以保留。

    让我们通过一个例子来看区别: 正确用法:

    function UserProfile() {
     // 正确:Hooks 在顶层调用
     const [name, setName] = useState('John');
     const [country, setCountry] = useState('US');
    
     return (
       <div>
         <h1>姓名:{name}</h1>
         <p>国家:{country}</p>
       </div>
     );
    }
    

    错误用法:

    function UserProfile() {
     const [name, setName] = useState('John');
    
     if (name === 'John') {
       // 错误:useState 在条件语句内部调用
       const [country, setCountry] = useState('US'); 
     }
    
     return (
       <div>
         <h1>姓名:{name}</h1>
         <p>国家:{country}</p> {/* 如果 name 条件不满足,这将抛出错误 */}
       </div>
     );
    }
    

    国家字段的 useState Hook 在 if 块中被有条件地调用。这可能导致不一致的状态行为,并可能导致 Hooks 在每次重新渲染时以不同的顺序被调用。

    1. 只从 React 函数中调用 Hooks:您不应该从常规的 JavaScript 函数或类组件中调用 Hooks。相反,您应该从函数组件或自定义 Hooks 中调用它们。

    让我们通过以下示例找出正确和错误用法的区别:

    正确用法:

    // 示例 1:
    function Counter() {
     // 正确:useState 在函数组件内部使用
     const [count, setCount] = useState(0);
    
     return <div>计数器:{count}</div>;
    }
    // 示例 2:
    function useFetchData(url) {
     const [data, setData] = useState(null);
    
     useEffect(() => {
       fetch(url)
         .then((response) => response.json())
         .then((data) => setData(data));
     }, [url]);
    
     return data;
    }
    
    function UserProfile() {
     // 正确:在此处使用自定义 Hook
     const user = useFetchData('https://some-api.com/user');
    
     return (
       <div>
         <h1>{user ? user.name : '加载个人资料中...'}</h1>
       </div>
     );
    }
    

    错误用法:

     // 示例 1
     function normalFunction() {
       // 错误:不能在普通函数中调用 Hooks
       const [count, setCount] = useState(0); 
     }
    
     // 示例 2
     function fetchData(url) {
       // 错误:Hooks 不能在非 React 函数中使用
       const [data, setData] = useState(null);
    
       useEffect(() => {
         fetch(url)
           .then((response) => response.json())
           .then((data) => setData(data));
       }, [url]);
    
       return data;
     }
    

    在上面的错误用法示例中,useStateuseEffect 都在非 React 函数(normalFunctionfetchData)中使用,这是不允许的。

⬆ 返回顶部

  1. 如何确保项目中遵循了 Hooks 规则?

    React 团队发布了一个名为 eslint-plugin-react-hooks 的 ESLint 插件,用于强制执行 Hook 的两条规则。它是 Hooks API 的一部分。您可以使用以下命令将此插件添加到您的项目中:

    npm install eslint-plugin-react-hooks --save-dev
    

    并在您的 ESLint 配置文件中应用以下配置:

    // 您的 ESLint 配置
    {
      "plugins": [
        // ...
        "react-hooks"
      ],
      "rules": {
        // ...
        "react-hooks/rules-of-hooks": "error"
      }
    }
    

    该插件还通过 react-hooks/exhaustive-deps 提供了另一个重要规则。它确保 useEffect、useCallback 和 useMemo Hooks 的依赖项被正确列出,以避免潜在的错误。

    useEffect(() => {
      // 忘记 `message` 将导致不正确的行为
      console.log(message);
    }, []); // 这里 `message` 应该是一个依赖项
    

    推荐的 eslint-config-react-app 预设已经包含了此插件的 Hooks 规则。 例如,linter 强制要求 Hooks 的命名约定。如果您将带有前缀 "use" 的自定义 Hooks 重命名为其他名称,那么 linter 将不再允许您在自定义 Hook 内部调用诸如 useState、useEffect 等内置 Hooks。

    注意: 此插件默认情况下旨在用于 Create React App。

⬆ 返回顶部

  1. Flux 和 Redux 之间有什么区别?

    以下是 Flux 和 Redux 之间的主要区别:

    Flux Redux
    状态是可变的 状态是不可变的
    Store 包含状态和变更逻辑 Store 和变更逻辑是分离的
    可以存在多个 Store 只存在一个 Store
    所有 Store 是相互独立且扁平的 单个 Store 带有层级化的 reducers
    有一个单例 dispatcher 没有 dispatcher 的概念
    React 组件订阅 Store 容器组件使用 connect 函数

⬆ 返回顶部

  1. React Router V4 的优点是什么?

    以下是 React Router V4 模块的主要优点:

    1. 在 React Router v4(版本 4)中,API 完全围绕组件。路由器可以被可视化为一个单一的组件(<BrowserRouter>),它包装了特定的子路由器组件(<Route>)。
    2. 您不需要手动设置 history。路由器模块会通过用 <BrowserRouter> 组件包装路由来处理 history。
    3. 通过仅添加特定的路由器模块(Web、core 或 native),减小了应用程序的大小。

⬆ 返回顶部

  1. 你能描述一下 componentDidCatch 生命周期方法的签名吗?

    componentDidCatch 生命周期方法在后代组件抛出错误后被调用。该方法接收两个参数:

    1. error: - 被抛出的错误对象
    2. info: - 一个具有 componentStack 键的对象,包含有关哪个组件抛出错误的信息。

    方法结构如下:

    componentDidCatch(error, info);
    

⬆ 返回顶部

  1. 在哪些场景下错误边界(error boundaries)不会捕获错误?

    以下是错误边界不起作用的情况:

    1. 事件处理程序内部
    2. 使用 setTimeout 或 requestAnimationFrame 回调的异步代码
    3. 服务器端渲染期间
    4. 当错误在错误边界自身的代码中抛出时

⬆ 返回顶部

  1. React 16 中未捕获错误的行为是什么?

    注意: 此行为在 React 16 中引入,并在 React 18/19 中继续存在。

    在 React 16+ 中,任何未被错误边界捕获的错误都将导致整个 React 组件树被卸载。做出此决定的原因是,留下损坏的 UI 比完全移除它更糟糕。例如,对于支付应用来说,显示错误的金额比不显示任何内容更糟糕。

    最佳实践: 始终将您的应用程序或关键部分包装在错误边界中,以防止完全卸载并提供更好的用户体验。

    <ErrorBoundary fallback={<ErrorPage />}>
      <App />
    </ErrorBoundary>
    

⬆ 返回顶部

  1. 错误边界的正确放置位置是什么?

    错误边界使用的粒度取决于开发者基于项目需求的决定。您可以采用以下任一方法:
    1. 您可以包装顶级路由组件,为整个应用程序显示通用错误消息。
    2. 您也可以将单个组件包装在错误边界中,以防止它们崩溃应用程序的其余部分。

⬆ 返回顶部

  1. 错误边界提供的组件堆栈跟踪有什么好处?

    除了错误消息和 JavaScript 堆栈外,React 16 会利用错误边界概念显示带有文件名和行号的组件堆栈跟踪。

    例如,BuggyCounter 组件会如下显示组件堆栈跟踪:

    stacktrace

⬆ Back to Top

  1. 什么是默认 props?

    defaultProps 可以定义为组件的一个属性,用于为 props 设置默认值。这些默认 props 在未提供 props(即 props 为 undefined)时使用,但不适用于 null0 作为 props 的情况。也就是说,如果你提供了 null 值,它将保持 null;对于 0 也是同样的行为。

    例如,我们为按钮组件创建 color 默认 prop:

    function MyButton {
      // ...
    }
    
    MyButton.defaultProps = {
      color: "red",
    };
    

    如果未提供 props.color,则会将默认值设置为 'red'。即,当你尝试访问 color prop 时,它会使用默认值。

    function MyButton() {
      return <MyButton />; // props.color 将包含 red 值
    }
    

⬆ Back to Top

  1. displayName 类属性的作用是什么?

    displayName 字符串用于调试消息。通常,你不需要显式设置它,因为它会从定义组件的函数或类的名称中推断出来。如果你希望为调试目的显示不同的名称,或者在创建高阶组件时,可能需要显式设置它。

    例如,为了便于调试,选择一个能表明它是 withSubscription HOC 结果的 displayName。

    function withSubscription(WrappedComponent) {
      class WithSubscription extends React.Component {
        /* ... */
      }
      WithSubscription.displayName = `WithSubscription(${getDisplayName(
        WrappedComponent
      )})`;
      return WithSubscription;
    }
    function getDisplayName(WrappedComponent) {
      return (
        WrappedComponent.displayName || WrappedComponent.name || "Component"
      );
    }
    

⬆ Back to Top

  1. React 应用程序的浏览器支持情况如何?

    React 支持所有主流浏览器,包括 Internet Explorer 9 及更高版本,不过对于较旧的浏览器(如 IE 9 和 IE 10),可能需要一些 polyfill。如果你使用 es5-shim 和 es5-sham polyfill,那么它甚至可以支持那些不支持 ES5 方法的旧浏览器。

⬆ Back to Top

  1. 什么是代码分割(code-splitting)?

    代码分割是 Webpack 和 Browserify 等打包工具支持的一项功能,它可以创建多个 bundle,并在运行时动态加载。React 项目通过动态 import() 特性支持代码分割。

    例如,在下面的代码片段中,moduleA.js 及其所有唯一依赖项将被打包成一个单独的 chunk,仅在用户点击“Load”按钮后才加载。

    moduleA.js

    const moduleA = "Hello";
    
    export { moduleA };
    

    App.js

    export default function App {
      function handleClick() {
        import("./moduleA")
          .then(({ moduleA }) => {
            // 使用 moduleA
          })
          .catch((err) => {
            // 处理加载失败
          });
      };
    
     return (
       <div>
         <button onClick={this.handleClick}>Load</button>
       </div>
     );
    }
    
See Class

    import React, { Component } from "react";

     class App extends Component {
       handleClick = () => {
         import("./moduleA")
           .then(({ moduleA }) => {
             // Use moduleA
           })
           .catch((err) => {
             // Handle failure
           });
       };

       render() {
         return (
           <div>
             <button onClick={this.handleClick}>Load</button>
           </div>
         );
       }
     }

     export default App;

⬆ Back to Top

  1. 什么是带键的片段(Keyed Fragments)?

    使用显式 <React.Fragment> 语法声明的片段可以拥有键(keys)。常见的使用场景是将一个集合映射为片段数组,如下所示:

    function Glossary(props) {
      return (
        <dl>
          {props.items.map((item) => (
            // 如果没有 `key`,React 会触发键警告
            <React.Fragment key={item.id}>
              <dt>{item.term}</dt>
              <dd>{item.description}</dd>
            </React.Fragment>
          ))}
        </dl>
      );
    }
    

    注意: key 是唯一可以传递给 Fragment 的属性。未来可能会支持其他属性,例如事件处理程序。

⬆ Back to Top

  1. React 是否支持所有 HTML 属性?

    从 React 16 开始,标准 DOM 属性和自定义 DOM 属性都得到了完全支持。由于 React 组件通常同时接收自定义属性和与 DOM 相关的属性,React 采用了与 DOM API 类似的驼峰式命名(camelCase)约定。

    以下是一些与标准 HTML 属性对应的 props 示例:

    <div tabIndex="-1" />      // 类似于 node.tabIndex DOM API
    <div className="Button" /> // 类似于 node.className DOM API
    <input readOnly={true} />  // 类似于 node.readOnly DOM API
    

    这些 props 的工作方式与相应的 HTML 属性类似,但有一些特殊情况除外。React 也支持所有 SVG 属性。

⬆ Back to Top

  1. 组件 props 在什么情况下会默认设为 true?

    如果你不为某个 prop 传递值,它会默认设为 true。这种行为是为了与 HTML 的行为保持一致。

    例如,以下表达式是等效的:

    <MyInput autocomplete />
    
    <MyInput autocomplete={true} />
    

    注意: 不建议使用这种方式,因为它可能会与 ES6 对象的简写语法(例如 {name} 等价于 {name: name})产生混淆。

⬆ Back to Top

  1. 什么是 NextJS 及其主要特性?

    Next.js 是一个流行且轻量级的框架,用于构建基于 React 的静态和服务端渲染应用。它还提供了样式和路由解决方案。以下是 NextJS 提供的主要特性:

    1. 默认支持服务端渲染
    2. 自动代码分割,加快页面加载速度
    3. 简单的客户端路由(基于页面)
    4. 基于 Webpack 的开发环境,支持热模块替换(HMR)
    5. 能够与 Express 或任何其他 Node.js HTTP 服务器集成
    6. 可通过自定义 Babel 和 Webpack 配置进行个性化设置

⬆ Back to Top

  1. 如何将事件处理程序传递给组件?

    你可以将事件处理程序和其他函数作为 props 传递给子组件。函数可以按以下方式传递给子组件:

    function Button({ onClick }) {
      return <button onClick={onClick}>Download</button>;
    }
    
    export default function downloadExcel() {
      function handleClick() {
        alert("Downloaded");
      }
    
      return <Button onClick={handleClick}></Button>;
    }
    

⬆ Back to Top

  1. 如何防止函数被多次调用?

    如果你使用如 onClick 或 onScroll 之类的事件处理程序,并且希望防止回调函数触发过快,可以限制回调函数的执行频率。这可以通过以下几种方式实现:

    1. 节流(Throttling): 基于时间频率进行变化。例如,可以使用 lodash 的 _.throttle 函数。
    2. 防抖(Debouncing): 在一段不活动期后发布变化。例如,可以使用 lodash 的 _.debounce 函数。
    3. RequestAnimationFrame 节流: 基于 requestAnimationFrame 进行变化。例如,可以使用 lodash 的 raf-schd 函数。

⬆ Back to Top

  1. JSX 如何防止注入攻击?

    React DOM 会在渲染前转义嵌入在 JSX 中的任何值。因此,它确保你永远无法注入任何未在应用程序中显式编写的内容。所有内容在渲染前都会被转换为字符串。

    例如,你可以像下面这样嵌入用户输入:

    const name = response.potentiallyMaliciousInput;
    const element = <h1>{name}</h1>;
    

    这样可以防止应用程序中的 XSS(跨站脚本)攻击。

⬆ Back to Top

  1. 如何更新已渲染的元素?

    你可以通过将新创建的元素传递给 ReactDOM 的 render 方法来更新 UI(由已渲染元素表示)。

    例如,让我们以一个滴答作响的时钟为例,它通过多次调用 render 方法来更新时间:

    function tick() {
      const element = (
        <div>
          <h1>Hello, world!</h1>
          <h2>现在是 {new Date().toLocaleTimeString()}。</h2>
        </div>
      );
      ReactDOM.render(element, document.getElementById("root"));
    }
    
    setInterval(tick, 1000);
    

⬆ Back to Top

  1. 为什么说 props 是只读的?

    当你将组件声明为函数或类时,它绝不能修改自己的 props。

    让我们来看下面这个计算本金的函数:

    function capital(amount, interest) {
      return amount + interest;
    }
    

    上面的函数被称为“纯函数”,因为它不会尝试更改输入,并且对于相同的输入总是返回相同的结果。因此,React 有一条规则:“所有 React 组件在使用 props 时都必须像纯函数一样行事。”

⬆ Back to Top

  1. 安全使用索引作为 key 的条件是什么?

    要确保安全地使用索引作为 key,需要满足三个条件:

    1. 列表和列表项是静态的——它们不是计算出来的,也不会改变。
    2. 列表项没有 id。
    3. 列表从不重新排序或筛选。

⬆ Back to Top

  1. keys 是否需要全局唯一?

    数组中使用的 keys 在其兄弟节点之间应该是唯一的,但它们不需要全局唯一。也就是说,你可以在两个不同的数组中使用相同的 keys。

    例如,下面的 Book 组件在两个不同的数组中使用了相同的 keys:

    function Book(props) {
      const index = (
        <ul>
          {props.pages.map((page) => (
            <li key={page.id}>{page.title}</li>
          ))}
        </ul>
      );
      const content = props.pages.map((page) => (
        <div key={page.id}>
          <h3>{page.title}</h3>
          <p>{page.content}</p>
          <p>{page.pageNumber}</p>
        </div>
      ));
      return (
        <div>
          {index}
          <hr />
          {content}
        </div>
      );
    }
    

⬆ Back to Top

  1. 表单处理的常用选择是什么?

    Formik 是一个 React 表单库,它提供了诸如验证、跟踪已访问字段以及处理表单提交等解决方案。

    具体来说,你可以将其功能归类如下:

    1. 在表单状态中获取和设置值
    2. 验证和错误消息
    3. 处理表单提交

    它用于创建一个可扩展、高性能的表单辅助工具,通过最小的 API 来解决一些麻烦的问题。

⬆ Back to Top

  1. Formik 相比 Redux Form 库有哪些优势?

    以下是推荐使用 Formik 而非 Redux Form 库的主要原因:

    1. 表单状态本质上是短期且局部的,因此在 Redux(或任何类型的 Flux 库)中跟踪它是不必要的。
    2. Redux-Form 会在每一次按键时多次调用整个顶级 Redux reducer。这会增加大型应用的输入延迟。
    3. Redux-Form 经过 minified 和 gzipped 后大小为 22.5 kB,而 Formik 为 12.7 kB。

⬆ Back to Top

  1. 为什么不一定要使用继承?

    在 React 中,建议使用组合而非继承来在组件之间复用代码。Props 和组合为你提供了所有必要的灵活性,可以显式且安全地自定义组件的外观和行为。 而如果你想在组件之间复用非 UI 功能,建议将其提取到一个单独的 JavaScript 模块中。然后组件可以导入并使用该函数、对象或类,而无需对其进行扩展。

⬆ Back to Top

  1. 能否在 React 应用中使用 Web Components?

    是的,你可以在 React 应用中使用 Web Components。尽管许多开发人员不会使用这种组合,但如果你正在使用第三方用 Web Components 编写的 UI 组件,这可能是必要的。

    例如,让我们如下使用 Vaadin 日期选择器 Web Component:

    import "./App.css";
    import "@vaadin/vaadin-date-picker";
    export default function App() {
      return (
        <div className="App">
          <vaadin-date-picker label="你什么时候出生的?"></vaadin-date-picker>
        </div>
      );
    }
    

⬆ Back to Top

  1. 什么是动态导入(dynamic import)?

    你可以使用动态导入在应用中实现代码分割。

    让我们以加法为例:

    1. 普通导入
    import { add } from "./math";
    console.log(add(10, 20));
    
    1. 动态导入
    import("./math").then((math) => {
      console.log(math.add(10, 20));
    });
    

⬆ Back to Top

  1. 什么是 Loadable Components?

    随着 React 18 的发布,React.lazy 和 Suspense 现在可用于服务端渲染。然而,在 React 18 之前,建议在服务端渲染的应用中使用 Loadable Components 进行代码分割,因为当时 React.lazy 和 Suspense 还不支持服务端渲染。Loadable Components 允许你将动态导入渲染为常规组件。例如,你可以使用 Loadable Components 将 OtherComponent 加载到一个单独的 bundle 中,如下所示:

    import loadable from "@loadable/component";
    
    const OtherComponent = loadable(() => import("./OtherComponent"));
    
    function MyComponent() {
      return (
        <div>
          <OtherComponent />
        </div>
      );
    }
    

    现在,OtherComponent 将被加载到一个单独的 bundle 中。 Loadable Components 除了代码分割之外,还提供了其他好处,例如自动代码重载、错误处理和预加载。通过使用 Loadable Components,你可以确保应用快速高效地加载,为用户提供更好的体验。

⬆ Back to Top

  1. 什么是 Suspense 组件?

    React Suspense 是一个内置功能,它允许你延迟渲染组件树的一部分,直到某个条件(异步操作)满足——通常是数据或代码加载完成。在等待期间,Suspense 允许你显示一个回退 UI,如加载指示器或占位符。

    1. 懒加载组件使用了 Suspense 功能:

      如果包含动态导入的模块在父组件渲染时尚未加载,你必须在等待其加载时显示一些回退内容,这可以使用 Suspense 组件来实现。

      const OtherComponent = React.lazy(() => import("./OtherComponent"));
      
      function MyComponent() {
        return (
          <div>
            <Suspense fallback={<div>加载中...</div>}>
              <OtherComponent />
            </Suspense>
          </div>
        );
      }
      

      上述组件在 OtherComponent 完全加载之前会显示回退 UI,而不是实际组件。

    2. 另一个例子:等待异步数据(数据获取)准备就绪

      function UserProfile() {
        const user = use(fetchUser()); // 内部抛出一个 promise
        return <div>{user.name}</div>;
      }
    
      function App() {
        return (
          <Suspense fallback={<div>加载用户中...</div>}>
            <UserProfile />
          </Suspense>
        );
      }
    
    
    

**[⬆ Back to Top](#table-of-contents)**

200. ### 什么是基于路由的代码分割?

     进行代码分割的最佳位置之一是在路由处。整个页面会一次性重新渲染,因此用户不太可能同时与页面中的其他元素进行交互。因此,用户体验不会受到干扰。

     让我们以一个使用 React Router 和 React.lazy 的基于路由的网站为例:

     ```javascript
     import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
     import React, { Suspense, lazy } from "react";

     const Home = lazy(() => import("./routes/Home"));
     const About = lazy(() => import("./routes/About"));

     const App = () => (
       <Router>
         <Suspense fallback={<div>加载中...</div>}>
           <Switch>
             <Route exact path="/" component={Home} />
             <Route path="/about" component={About} />
           </Switch>
         </Suspense>
       </Router>
     );
     ```

     在上面的代码中,代码分割将在每个路由级别进行。

**[⬆ Back to Top](#table-of-contents)**

201. ### Context 中默认值的作用是什么?

     defaultValue 参数仅在组件树中没有匹配的 Provider 时使用。这有助于在隔离环境中测试组件,而无需对其进行包装。

     下面的代码片段提供了默认主题值 Luna。

     ```javascript
     const MyContext = React.createContext(defaultValue);
     ```
**[⬆ Back to Top](#table-of-contents)**

202. ### 什么是差异算法(diffing algorithm)?

     React 需要使用算法来确定如何有效地更新 UI 以匹配最新的树。差异算法用于生成将一棵树转换为另一棵树所需的最少操作数。然而,这些算法的复杂度为 O(n³),其中 n 是树中元素的数量。

     在这种情况下,显示 1000 个元素将需要大约 10 亿次比较。这开销太大了。相反,React 实现了一个基于两个假设的启发式 O(n) 算法:

     1. 不同类型的元素将生成不同的树。
     2. 开发人员可以通过 key prop 提示哪些子元素在不同的渲染中可能是稳定的。

**[⬆ Back to Top](#table-of-contents)**

203. ### 差异算法涵盖哪些规则?

     当比较两棵树时,React 首先比较两个根元素。根据根元素的类型,行为会有所不同。在协调算法期间,它涵盖以下规则:

     1. **不同类型的元素:**
        只要根元素的类型不同,React 就会拆除旧树并从头开始构建新树。例如,元素从 <a> 变为 <img>,或从 <Article> 变为 <Comment> 等不同类型会导致完全重建。
     2. **相同类型的 DOM 元素:**
        当比较两个相同类型的 React DOM 元素时,React 会查看两者的属性,保留相同的底层 DOM 节点,只更新已更改的属性。让我们看一个除了 className 属性外其他都相同的 DOM 元素示例:

        ```javascript
        <div className="show" title="ReactJS" />

        <div className="hide" title="ReactJS" />
        ```

     3. **相同类型的组件元素:**
        当组件更新时,实例保持不变,因此状态会在渲染之间得到维护。React 会更新底层组件实例的 props 以匹配新元素,并在底层实例上调用 componentWillReceiveProps() 和 componentWillUpdate()。之后,调用 render() 方法,差异算法会递归比较先前的结果和新的结果。
     4. **递归子节点:**
        当递归 DOM 节点的子节点时,React 只是同时迭代两个子节点列表,并在发现差异时生成一个突变。例如,在子节点末尾添加元素时,在这两棵树之间进行转换效果很好。

        ```javascript
        <ul>
          <li>first</li>
          <li>second</li>
        </ul>

        <ul>
          <li>first</li>
          <li>second</li>
          <li>third</li>
        </ul>

        ```

     5. **处理 keys:**
        React 支持 key 属性。当子节点有 keys 时,React 使用 key 来匹配原始树中的子节点和后续树中的子节点。例如,添加 key 可以使树转换更高效:

     ```javascript
     <ul>
       <li key="2015">Duke</li>
       <li key="2016">Villanova</li>
     </ul>

     <ul>
       <li key="2014">Connecticut</li>
       <li key="2015">Duke</li>
       <li key="2016">Villanova</li>
     </ul>
     ```
**[⬆ Back to Top](#table-of-contents)**

204. ### 何时需要使用 refs?

     以下是适合使用 refs 的一些场景:

     1. 管理焦点、文本选择或媒体播放。
     2. 触发命令式动画。
     3. 与第三方 DOM 库集成。

**[⬆ Back to Top](#table-of-contents)**

205. ### render props 的 prop 必须命名为 render 吗?

     尽管这种模式被命名为 render props,但你不必使用名为 render 的 prop 来使用此模式。也就是说,任何作为函数且组件用于确定渲染内容的 prop,在技术上都是“render prop”。让我们看一个使用 children prop 作为 render prop 的例子:

     ```javascript
     <Mouse
       children={(mouse) => (
         <p>
           鼠标位置是 {mouse.x}, {mouse.y}
         </p>
       )}
     />
     ```

     实际上,children prop 不需要在 JSX 元素的“属性”列表中命名。相反,你可以直接将它放在元素内部:

     ```javascript
     <Mouse>
       {(mouse) => (
         <p>
           鼠标位置是 {mouse.x}, {mouse.y}
         </p>
       )}
     </Mouse>
     ```

     当使用上述技术(不指定名称)时,请在 propTypes 中明确声明 children 应该是一个函数。

     ```javascript
     Mouse.propTypes = {
       children: PropTypes.func.isRequired,
     };
     ```
**[⬆ Back to Top](#table-of-contents)**

206. ### 在纯组件中使用 render props 会有什么问题?
     如果你在 render 方法内部创建一个函数,这会使纯组件失去意义。因为对新 props 的浅层比较将始终返回 false,并且在这种情况下每次渲染都会为 render prop 生成一个新值。你可以通过将 render 函数定义为实例方法来解决此问题。

**[⬆ Back to Top](#table-of-contents)**

207. ### 什么是窗口化技术(windowing technique)?
     窗口化是一种仅在任何给定时间渲染一小部分行的技术,它可以显著减少重新渲染组件所需的时间以及创建的 DOM 节点数量。如果你的应用程序渲染长列表数据,那么推荐使用这种技术。react-window 和 react-virtualized 都是流行的窗口化库,它们提供了几个可重用的组件,用于显示列表、网格和表格数据。

**[⬆ Back to Top](#table-of-contents)**

208. ### 如何在 JSX 中打印假值?

     像 false、null、undefined 和 true 这样的假值是有效的子元素,但它们不会渲染任何内容。如果你仍然想显示它们,就需要将其转换为字符串。下面是一个转换为字符串的示例:

     ```javascript
     <div>我的 JavaScript 变量是 {String(myVariable)}。</div>
     ```
**[⬆ 返回顶部](#table-of-contents)**

209. ### Portals 的典型用例是什么?
     React Portals 主要用于在其父组件的 DOM 树之外渲染 UI 组件,例如**模态框、工具提示、下拉菜单、悬停卡片和通知**。这有助于避免由父元素引起的常见 CSS 问题,例如:

     *   父元素上的 `**overflow: hidden**` 会裁剪或隐藏模态框、工具提示等子元素,
     *   父容器创建的**堆叠上下文和** `**z-index**` **冲突**,导致子元素无法显示在其他内容之上。

     也就是说,你需要让这些 UI 元素在视觉上“突破”其容器。通过将这些 UI 元素渲染到一个单独的 DOM 节点(通常直接在 `<body>` 下),Portals 确保它们显示在所有其他内容之上,并且不受父元素的 CSS 或布局约束的限制,从而无论父元素的样式如何,都能实现正确的定位和可见性。

**[⬆ 返回顶部](#table-of-contents)**

210.   ### 如何为非受控组件设置默认值?
 在 React 中,表单元素上的 value 属性会覆盖 DOM 中的值。对于非受控组件,你可能希望 React 指定初始值,但后续更新不受控制。要处理这种情况,你可以指定 **defaultValue** 属性而不是 **value**。

     ```javascript
     render() {
       return (
         <form onSubmit={this.handleSubmit}>
           <label>
             用户名:
             <input
               defaultValue="John"
               type="text"
               ref={this.input} />
           </label>
           <input type="submit" value="提交" />
         </form>
       );
     }
     ```

     这同样适用于 `select` 和 `textArea` 输入。但对于 `checkbox` 和 `radio` 输入,你需要使用 **defaultChecked**。

**[⬆ 返回顶部](#table-of-contents)**

211. ### 你最喜欢的 React 技术栈是什么?
     尽管技术栈因开发者而异,但最流行的栈用于 React 样板项目代码中。它主要使用 Redux 和 redux-saga 进行状态管理和异步副作用处理,react-router 用于路由,styled-components 用于 React 组件样式,axios 用于调用 REST API,以及其他支持性技术栈,如 webpack、reselect、ESNext、Babel。
     你可以克隆项目 https://github.com/react-boilerplate/react-boilerplate 并开始处理任何新的 React 项目。

**[⬆ 返回顶部](#table-of-contents)**

212. ### Real DOM 和 Virtual DOM 有什么区别?

     以下是 Real DOM 和 Virtual DOM 之间的主要区别:

     | Real DOM(真实 DOM)                | Virtual DOM(虚拟 DOM)              |
     | ------------------------------------ | ------------------------------------ |
     | 更新速度慢                           | 更新速度快                           |
     | DOM 操作成本很高。                    | DOM 操作非常简单                     |
     | 可以直接更新 HTML。                  | 不能直接更新 HTML                    |
     | 会导致大量内存浪费                   | 没有内存浪费                         |
     | 如果元素更新,会创建一个新的 DOM     | 如果元素更新,它会更新 JSX           |

**[⬆ 返回顶部](#table-of-contents)**

213. ### 如何将 Bootstrap 添加到 React 应用程序?

     可以通过三种可能的方式将 Bootstrap 添加到你的 React 应用中:

     1. 使用 Bootstrap CDN:
        这是添加 Bootstrap 最简单的方法。在 head 标签中添加 Bootstrap 的 CSS 和 JS 资源。
     2. 作为依赖项安装 Bootstrap:
        如果你使用构建工具或模块打包器(如 Webpack),那么这是将 Bootstrap 添加到 React 应用程序的首选选项。
        ```javascript
        npm install bootstrap
        ```
     3. React Bootstrap 包:
        在这种情况下,你可以通过使用一个专门将 Bootstrap 组件重新构建为 React 组件的包来将 Bootstrap 添加到 React 应用中。以下是该类别中受欢迎的包:
        1. react-bootstrap
        2. reactstrap

**[⬆ 返回顶部](#table-of-contents)**

214. ### 你能列出使用 React 作为前端框架的顶级网站或应用程序吗?

     以下是使用 React 作为前端框架的 `top 10 websites`:

     1. Facebook(脸书)
     2. Uber(优步)
     3. Instagram(照片墙)
     4. WhatsApp(瓦次普)
     5. Khan Academy(可汗学院)
     6. Airbnb(爱彼迎)
     7. Dropbox( Dropbox 云存储)
     8. Flipboard(新闻聚合应用)
     9. Netflix(奈飞)
     10. PayPal(贝宝)

**[⬆ 返回顶部](#table-of-contents)**

215. ### 在 React 中推荐使用 CSS In JS 技术吗?
     React 对样式的定义没有任何意见,但如果你是初学者,一个好的起点是像往常一样在单独的 \*.css 文件中定义样式,并使用 className 引用它们。此功能不属于 React,而是来自第三方库。但如果你想尝试不同的方法(CSS-In-JS),那么 styled-components 库是一个不错的选择。

**[⬆ 返回顶部](#table-of-contents)**

216. ### 我需要用 Hooks 重写所有的类组件吗?
     不需要。但你可以在一些组件(或新组件)中尝试 Hooks,而无需重写任何现有代码。因为 ReactJS 没有计划移除类组件。

**[⬆ 返回顶部](#table-of-contents)**

217. ### 什么是 useEffect Hook?如何使用 React Hooks 获取数据?

     `useEffect` Hook 是一个 React Hook,它允许你在函数组件中执行**副作用**操作。副作用是与外部世界或系统交互且与渲染 UI 没有直接关系的操作,例如获取数据、设置订阅、计时器、手动操作 DOM 等。

     在函数组件中,useEffect 用一个统一的 API 替代了类组件的生命周期方法(`componentDidMount`、`componentDidUpdate` 和 `componentWillUnmount`)。     

     **语法**
     ```js
     useEffect(() => {
        // 副作用逻辑在这里

        return () => {
        // 清理逻辑(可选)
        };
        }, [dependencies]);
     ```
     这个 effect Hook 可用于从 API 获取数据,并使用 useState Hook 的更新函数将数据设置到组件的本地状态中。

     以下是使用 fetch 从 API 获取 ReactJS 文章列表的示例。

     ```javascript
     import React from "react";

     function App() {
       const [data, setData] = React.useState({ hits: [] });

       React.useEffect(() => {
         fetch("http://hn.algolia.com/api/v1/search?query=react")
           .then((response) => response.json())
           .then((data) => setData(data));
       }, []);

       return (
         <ul>
           {data.hits.map((item) => (
             <li key={item.objectID}>
               <a href={item.url}>{item.title}</a>
             </li>
           ))}
         </ul>
       );
     }

     export default App;
     ```

     一种流行的简化方法是使用 axios 库。

     我们为 useEffect Hook 提供了一个空数组作为第二个参数,以避免在组件更新时激活它。这样,它只在组件挂载时获取数据。

**[⬆ 返回顶部](#table-of-contents)**

218. ### Hooks 是否涵盖了类组件的所有用例?
     Hooks 并未涵盖类组件的所有用例,但计划很快会添加。目前,对于不太常见的 **getSnapshotBeforeUpdate** 和 **componentDidCatch** 生命周期,还没有对应的 Hook。

**[⬆ 返回顶部](#table-of-contents)**

219. ### Hooks 支持的稳定版本是什么?

     React 在 16.8 版本中为以下包包含了 React Hooks 的稳定实现:

     1. React DOM
     2. React DOM Server
     3. React Test Renderer
     4. React Shallow Renderer

**[⬆ 返回顶部](#table-of-contents)**

220. ### 为什么在 `useState` 中使用数组解构(方括号表示法)?

     当我们使用 `useState` 声明一个状态变量时,它会返回一个对——一个包含两个元素的数组。第一个元素是当前值,第二个是更新该值的函数。使用 [0] 和 [1] 来访问它们有点令人困惑,因为它们有特定的含义。这就是我们使用数组解构的原因。

     例如,数组索引访问如下所示:

     ```javascript
     var userStateVariable = useState("userProfile"); // 返回一个数组对
     var user = userStateVariable[0]; // 访问第一个元素
     var setUser = userStateVariable[1]; // 访问第二个元素
     ```

     而使用数组解构时,变量可以按如下方式访问:

     ```javascript
     const [user, setUser] = useState("userProfile");
     ```

     **[⬆ 返回顶部](#table-of-contents)**

221. ### 引入 Hooks 的灵感来源于哪些方面?

     Hooks 的灵感来源于几个不同的方面。以下是其中一些:

     1. react-future 仓库中对函数式 API 的早期实验
     2. 社区对 render prop API 的实验,例如 Reactions Component
     3. DisplayScript 中的状态变量和状态单元。
     4. Rx 中的订阅。
     5. ReasonReact 中的 Reducer 组件。

**[⬆ 返回顶部](#table-of-contents)**

222. ### 如何访问 Web Components 的命令式 API?
     Web Components 通常会公开一个命令式 API 来实现其功能。如果你想访问 Web Component 的命令式 API,需要使用 **ref** 直接与 DOM 节点交互。但如果你使用的是第三方 Web Components,最好的解决方案是编写一个 React 组件作为你的 Web Component 的**包装器**。

**[⬆ 返回顶部](#table-of-contents)**

223. ### 什么是 formik?

     Formik 是一个小型的 React 表单库,它可以帮助你解决三个主要问题:

     1. 在表单状态中获取和设置值
     2. 验证和错误消息
     3. 处理表单提交

**[⬆ 返回顶部](#table-of-contents)**

224. ### 在 Redux 中处理异步调用的典型中间件选择有哪些?
     Redux 生态系统中处理异步调用的一些流行中间件选择是 `Redux Thunk、Redux Promise、Redux Saga`。

**[⬆ 返回顶部](#table-of-contents)**

225. ### 浏览器能理解 JSX 代码吗?
     不能,浏览器无法理解 JSX 代码。你需要一个转译器将 JSX 转换为浏览器可以理解的常规 JavaScript。目前使用最广泛的转译器是 Babel。

**[⬆ 返回顶部](#table-of-contents)**

226. ### 描述一下 React 中的数据流?
     React 使用 props 实现单向响应式数据流,这减少了样板代码,并且比传统的双向数据绑定更容易理解。

**[⬆ 返回顶部](#table-of-contents)**

227. ### 什么是 MobX?
     MobX 是一个简单、可扩展且经过实战测试的状态管理解决方案,用于应用函数式响应式编程(TFRP)。对于 ReactJS 应用程序,你需要安装以下包:
     ```bash
     npm install mobx --save
     npm install mobx-react --save
     ```
**[⬆ 返回顶部](#table-of-contents)**

228. ### Redux 和 MobX 有什么区别?

     以下是 Redux 和 MobX 之间的主要区别:

     | 主题         | Redux                                                         | MobX                                                                   |
     | ------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- |
     | 定义         | 它是一个用于管理应用程序状态的 JavaScript 库                  | 它是一个用于响应式管理应用程序状态的库                                  |
     | 编程语言     | 主要用 ES6 编写                                               | 用 JavaScript(ES5)编写                                               |
     | 数据存储     | 只有一个大型的存储库用于数据存储                              | 有多个存储库用于存储                                                   |
     | 用途         | 主要用于大型和复杂的应用程序                                  | 用于简单的应用程序                                                     |
     | 性能         | 需要改进                                                      | 提供更好的性能                                                         |
     | 存储方式     | 使用 JS 对象存储                                              | 使用 observable 存储数据                                               |

**[⬆ 返回顶部](#table-of-contents)**

229. ### 学习 ReactJS 之前需要学习 ES6 吗?

     不需要,你不必为了学习 React 而学习 es2015/es6。但是你可能会发现许多 React 资源或生态系统广泛使用 ES6。让我们看看一些常用的 ES6 特性:

     1. **解构赋值:** 获取 props 并在组件中使用它们

        ```javascript
        // 在 es5 中
        var someData = this.props.someData;
        var dispatch = this.props.dispatch;

        // 在 es6 中
        const { someData, dispatch } = this.props;
        ```

     2. **扩展运算符:** 帮助将 props 向下传递到组件中

        ```javascript
        // 在 es5 中
        <SomeComponent someData={this.props.someData} dispatch={this.props.dispatch} />

        // 在 es6 中
        <SomeComponent {...this.props} />
        ```

     3. **箭头函数:** 使语法更简洁
        ```javascript
        // es5
        var users = usersList.map(function (user) {
          return <li>{user.name}</li>;
        });
        // es6
        const users = usersList.map((user) => <li>{user.name}</li>);
        ```
**[⬆ 返回顶部](#table-of-contents)**

230. ### 什么是并发渲染(Concurrent Rendering)?

     并发渲染使 React 应用程序通过渲染组件树而不阻塞主 UI 线程,从而变得更具响应性。它允许 React 中断长时间运行的渲染以处理高优先级事件。也就是说,当你启用并发模式时,React 会关注其他需要完成的任务,如果有更高优先级的任务,它会暂停当前正在渲染的内容,让其他任务先完成。你可以通过两种方式启用它:

     ```javascript
     // 1. 通过用 ConcurrentMode 包装应用的一部分
     <React.unstable_ConcurrentMode>
       <Something />
     </React.unstable_ConcurrentMode>;

     // 2. 使用 createRoot 整个应用
     ReactDOM.unstable_createRoot(domNode).render(<App />);
     ```
**[⬆ 返回顶部](#table-of-contents)**

231. ### async mode 和 concurrent mode 有什么区别?
     两者指的是同一个东西。React 团队之前将并发模式称为“Async Mode”。更改名称是为了强调 React 能够在不同优先级级别上执行工作。因此,它避免了与其他异步渲染方法的混淆。

**[⬆ 返回顶部](#table-of-contents)**

233. ### Hooks 的 ESLint 插件的目的是什么?

     ESLint 插件强制执行 Hooks 规则以避免错误。它假设任何以“use”开头且紧跟一个大写字母的函数都是 Hook。特别是,该规则强制:

     1. Hooks 的调用要么在 PascalCase 函数(假设是组件)内部,要么在另一个 useSomething 函数(假设是自定义 Hook)内部。
     2. Hooks 在每次渲染时以相同的顺序被调用。

**[⬆ 返回顶部](#table-of-contents)**

234. ### React 中的命令式(Imperative)和声明式(Declarative)有什么区别?

     想象一个简单的 UI 组件,例如“点赞”按钮。当你点击它时,如果之前是灰色,它会变成蓝色;如果之前是蓝色,它会变成灰色。

     命令式的做法是:

     ```javascript
     if (user.likes()) {
       if (hasBlue()) {
         removeBlue();
         addGrey();
       } else {
         removeGrey();
         addBlue();
       }
     }
     ```

     基本上,你必须检查屏幕上当前的内容,并处理所有必要的更改以用当前状态重新绘制它,包括撤销先前状态的更改。你可以想象在现实场景中这会有多复杂。

     相比之下,声明式的方法是:

     ```javascript
     if (this.state.liked) {
       return <blueLike />;
     } else {
       return <greyLike />;
     }
     ```

     因为声明式方法分离了关注点,所以这部分只需要处理 UI 在特定状态下应该是什么样子,因此更容易理解。

**[⬆ 返回顶部](#table-of-contents)**

235. ### 在 ReactJS 中使用 TypeScript 有什么好处?

     以下是在 ReactJS 中使用 TypeScript 的一些好处:

     1. 可以使用最新的 JavaScript 特性
     2. 使用接口定义复杂类型
     3. 像 VS Code 这样的 IDE 是为 TypeScript 打造的
     4. 通过易读性和验证来避免错误

     **[⬆ 返回顶部](#table-of-contents)**

236. ### 在使用 Context API 状态管理时,如何确保用户在页面刷新时保持身份验证状态?

     当用户登录并重新加载页面时,为了持久化状态,通常我们会在主 App.js 的 useEffect 钩子中添加加载用户的操作。使用 Redux 时,可以轻松访问 loadUser 操作。

     **App.js**

     ```js
     import { loadUser } from "../actions/auth";
     store.dispatch(loadUser());
     ```

     - 但是在使用 **Context API** 时,要在 App.js 中访问上下文,需要在 index.js 中包装 AuthState,以便 App.js 可以访问 auth 上下文。现在,无论你在哪个路由上,只要页面重新加载,用户就会被认证,因为 **loadUser** 操作会在每次重新渲染时触发。

     **index.js**

     ```js
     import React from "react";
     import ReactDOM from "react-dom";
     import App from "./App";
     import AuthState from "./context/auth/AuthState";

     ReactDOM.render(
       <React.StrictMode>
         <AuthState>
           <App />
         </AuthState>
       </React.StrictMode>,
       document.getElementById("root")
     );
     ```

     **App.js**

     ```js
     const authContext = useContext(AuthContext);

     const { loadUser } = authContext;

     useEffect(() => {
       loadUser();
     }, []);
     ```

     **loadUser**

     ```js
     const loadUser = async () => {
       const token = sessionStorage.getItem("token");

       if (!token) {
         dispatch({
           type: ERROR,
         });
       }
       setAuthToken(token);

       try {
         const res = await axios("/api/auth");

         dispatch({
           type: USER_LOADED,
           payload: res.data.data,
         });
       } catch (err) {
         console.error(err);
       }
     };
     ```
**[⬆ 返回顶部](#table-of-contents)**

237. ### 新的 JSX 转换有什么好处?

     新的 JSX 转换有三个主要好处:

     1. 可以在不导入 React 包的情况下使用 JSX
     2. 编译后的输出可能会略微减小捆绑包大小
     3. 未来的改进提供了减少学习 React 概念数量的灵活性。

**[⬆ 返回顶部](#table-of-contents)**

238. ### 新的 JSX 转换与旧转换有何不同?

     新的 JSX 转换不需要 React 在作用域内。也就是说,在简单场景下,你无需导入 React 包。

     让我们通过一个示例来看新旧转换之间的主要区别,

     **旧转换:**

     ```js
     import React from "react";

     function App() {
       return <h1>Good morning!!</h1>;
     }
     ```

     现在,JSX 转换会将上述代码转换为如下的常规 JavaScript:

     ```js
     import React from "react";

     function App() {
       return React.createElement("h1", null, "Good morning!!");
     }
     ```

     **新转换:**

     新的 JSX 转换不需要任何 React 导入

     ```js
     function App() {
       return <h1>Good morning!!</h1>;
     }
     ```

     在底层,JSX 转换会编译为以下代码

     ```js
     import { jsx as _jsx } from "react/jsx-runtime";

     function App() {
       return _jsx("h1", { children: "Good morning!!" });
     }
     ```

     **注意:** 若要使用 Hooks,你仍然需要导入 React。

**[⬆ Back to Top](#table-of-contents)**

239. ### 什么是 React Server Components?

     React Server Component 是一种编写 React 组件的方式,该组件在服务器端进行渲染,目的是提高 React 应用的性能。这些组件允许我们从后端加载组件。

     **注意:** React Server Components 仍在开发中,尚不建议用于生产环境。

**[⬆ Back to Top](#table-of-contents)**

240. ### 什么是 prop drilling(属性穿透)?
     Prop Drilling 是指在 React 组件树中,将数据从一个组件传递到另一个组件时,需要经过那些并不需要该数据、仅起到传递作用的其他组件的过程。

**[⬆ Back to Top](#table-of-contents)**

241. ### useState 和 useRef Hook 有什么区别?
     1. useState 在状态更新后会导致组件重新渲染,而 useRef 在值或状态变化时不会导致组件重新渲染。
        本质上,useRef 就像一个“盒子”,可以在其(`.current`)属性中保存一个可变值。
     2. useState 允许我们在组件内部更新状态。而 useRef 允许引用 DOM 元素和跟踪值。

**[⬆ Back to Top](#table-of-contents)**

242. ### 什么是包装组件(wrapper component)?

     React 中的包装组件是一个用于包裹另一个组件或一组组件的组件。它可用于多种目的,例如为被包裹的组件添加额外功能、样式或布局。

     例如,考虑一个显示消息的简单组件:

     ```javascript
     const Message = ({ text }) => {
       return <p>{text}</p>;
     };
     ```

     我们可以创建一个包装组件,为消息组件添加边框:

     ```javascript
     const MessageWrapper = (props) => {
       return (
         <div style={{ border: "1px solid black" }}>
           <Message {...props} />
         </div>
       );
     };
     ```

     现在,我们可以使用 MessageWrapper 组件代替 Message 组件,消息将显示边框:

     ```javascript
     <MessageWrapper text="Hello World" />
     ```

     包装组件也可以接受自己的 props 并将它们传递给被包裹的组件,例如,我们可以创建一个为消息组件添加标题的包装组件:

     ```javascript
     const MessageWrapperWithTitle = ({ title, ...props }) => {
       return (
         <div>
           <h3>{title}</h3>
           <Message {...props} />
         </div>
       );
     };
     ```

     现在我们可以使用 MessageWrapperWithTitle 组件并传递 title 属性:

     ```javascript
     <MessageWrapperWithTitle title="My Message" text="Hello World" />
     ```

     通过这种方式,包装组件可以为被包裹的组件添加额外的功能、样式或布局,同时保持被包裹组件的简洁和可重用性。

**[⬆ Back to Top](#table-of-contents)**

243. ### useEffect 和 useLayoutEffect Hook 有什么区别?

     useEffect 和 useLayoutEffect 都是 React Hook,可用于将组件与外部系统(如浏览器 API 或第三方库)同步。但是,两者之间存在一些关键区别:

     - **执行时机**:useEffect 在浏览器完成绘制后运行,而 useLayoutEffect 在浏览器绘制前同步运行。这意味着 useLayoutEffect 可用于测量和更新布局,给用户的感觉更同步。

     - **浏览器绘制**:useEffect 允许浏览器在运行 effect 之前绘制更改,因此可能会导致一些视觉闪烁。useLayoutEffect 在浏览器绘制前同步运行 effect,因此可以避免视觉闪烁。

     - **执行顺序**:多个 useEffect Hook 的执行顺序由 React 决定,可能不可预测。然而,多个 useLayoutEffect Hook 的执行顺序由它们被调用的顺序决定。

     - **错误处理**:useEffect 有内置的机制来处理 effect 执行过程中发生的错误,因此不会导致整个应用崩溃。useLayoutEffect 没有此机制,effect 执行过程中发生的错误会导致整个应用崩溃。

     通常,建议尽可能使用 useEffect,因为它性能更好且不易出错。只有当你需要测量或更新布局,并且使用 useEffect 无法实现相同结果时,才应使用 useLayoutEffect。

**[⬆ Back to Top](#table-of-contents)**

244. ### 函数组件和类组件有什么区别?

     在 ReactJS 中有两种创建组件的不同方式。主要区别如下:

     ## 1. 语法:

     类组件使用 ES6 类来创建组件。它使用 `render` 函数在网页上显示 HTML 内容。

     类组件的语法如下所示。

     ```js
     class App extends React.Component {
       render() {
         return <h1>This is a class component</h1>;
       }
     }
     ```

     **注意:** 推荐使用**帕斯卡命名法**为组件命名。

     函数组件多年来不断改进,增加了诸如 Hooks 等功能。以下是函数组件的语法。

     ```js
     function App() {
       return (
         <div className="App">
           <h1>Hello, I'm a function component</h1>
         </div>
       );
     }
     ```

     ## 2. 状态(State):

     状态包含关于组件的信息或数据,这些信息或数据可能会随时间变化。

     在类组件中,当用户与组件交互或服务器更新数据时,你可以使用 `setState()` 方法更新状态。初始状态将在 `Constructor()` 方法中使用 `this.state` 对象进行分配,并且可以分配不同的数据类型,如字符串、布尔值、数字等。

     **一个展示如何使用 setState() 和 constructor() 的简单示例:**

     ```js
     class App extends Component {
       constructor() {
         super();
         this.state = {
           message: "This is a class component",
         };
       }
       updateMessage() {
         this.setState({
           message: "Updating the class component",
         });
       }
       render() {
         return (
           <>
             <h1>{this.state.message}</h1>
             <button
               onClick={() => {
                 this.updateMessage();
               }}>
               Click!!
             </button>
           </>
         );
       }
     }

     ```

     过去,函数组件中不能使用状态,因为状态仅在类组件中受支持。但多年来,函数组件中实现了 Hooks,这使得函数组件也能够使用状态。

     `useState()` Hook 可用于在函数组件中实现状态。它返回一个包含两个元素的数组:第一个元素是当前状态,下一个是更新当前状态值的函数(setState)。

     让我们看一个示例来演示函数组件中的状态:

     ```js
     function App() {
       const [message, setMessage] = useState("This is a functional component");
       const updateMessage = () => {
         setMessage("Updating the functional component");
       };
       return (
         <div className="App">
           <h1>{message} </h1>
           <button onClick={updateMessage}>Click me!!</button>
         </div>
       );
     }
     ```

     ## 3. 属性(Props):

     Props 被称为“属性”。Props 被传递到 React 组件中,就像传递给函数的参数一样。换句话说,它们类似于 HTML 属性。

     在子类组件中,可以使用 `this.props` 访问 props,如下例所示:

     ```js
     class Child extends React.Component {
       render() {
         return (
           <h1>
             {" "}
             This is a functional component and component name is {
               this.props.name
             }{" "}
           </h1>
         );
       }
     }

     class Parent extends React.Component {
       render() {
         return (
           <div className="Parent">
             <Child name="First child component" />
             <Child name="Second child component" />
           </div>
         );
       }
     }
     ```

     函数组件中的 Props 与类组件中的类似,但区别在于没有 'this' 关键字。

     ```js
     function Child(props) {
       return (
         <h1>
           This is a child component and the component name is{props.name}
         </h1>
       );
     }

     function Parent() {
       return (
         <div className="Parent">
           <Child name="First child component" />
           <Child name="Second child component" />
         </div>
       );
     }
     ```
**[⬆ Back to Top](#table-of-contents)**

245. ### React 中的严格模式(strict mode)是什么?

     `React.StrictMode` 是一个有用的组件,用于突出显示应用程序中的潜在问题。与 `<Fragment>` 类似,`<StrictMode>` 不会渲染任何额外的 DOM 元素。它会为其后代激活额外的检查和警告。这些检查仅适用于**开发模式**。

     ```jsx harmony
     import { StrictMode } from "react";

     function App() {
       return (
         <div>
           <Header />
           <StrictMode>
             <div>
               <ComponentOne />
               <ComponentTwo />
             </div>
           </StrictMode>
           <Header />
         </div>
       );
     }
     ```

     在上面的示例中,严格模式检查仅适用于 `<ComponentOne>` 和 `<ComponentTwo>` 组件。即,仅适用于应用程序的一部分。

     **注意:** 像 NextJS 这样的框架默认启用此标志。

     **[⬆ Back to Top](#table-of-contents)**

246. ### 严格模式的好处是什么?

     <StrictMode> 在以下情况会有所帮助:

     1. 发现由不纯渲染(组件会重新渲染两次)导致的错误。
     2. 发现由 effects 清理不完整(组件会额外再运行一次 effects)导致的错误。
     3. 识别具有**不安全生命周期方法**的组件。
     4. 警告**旧版字符串 ref** API 的使用。
     5. 检测意外的**副作用**。
     6. 检测**旧版 context** API。
     7. 警告已弃用的**findDOMNode** 用法

**[⬆ Back to Top](#table-of-contents)**

247. ### 为什么 React 的严格模式会渲染两次?

     严格模式在开发模式(非生产模式)下会将组件渲染两次,以检测代码中的任何问题并向你发出警告。这用于检测渲染阶段的意外副作用。如果你使用 `create-react-app` 开发工具,它会默认自动启用严格模式。

     ```js
     const root = createRoot(document.getElementById("root"));
     root.render(
       <StrictMode>
         <App />
       </StrictMode>
     );
     ```

     如果你想禁用此行为,只需移除 `strict` 模式即可。

     ```js
     const root = createRoot(document.getElementById("root"));
     root.render(<App />);
     ```

     为了检测副作用,以下函数会被调用两次:

     1. 函数组件体,不包括事件处理程序内部的代码。
     2. 传递给 `useState`、`useMemo` 或 `useReducer`(任何其他 Hook)的函数
     3. 类组件的 `constructor`、`render` 和 `shouldComponentUpdate` 方法
     4. 类组件静态 `getDerivedStateFromProps` 方法
     5. 状态更新函数

**[⬆ Back to Top](#table-of-contents)**

248. ### JSX 的规则是什么?

     在 React 应用中使用 JSX 时需要遵循以下 3 条规则。

     1. **返回单个根元素**:
        如果你从一个组件返回多个元素,需将它们包装在一个单个父元素中。否则,你将在浏览器控制台中收到以下错误。

        `html Adjacent JSX elements must be wrapped in an enclosing tag.`

     2. **所有标签都需要闭合**:
        与 HTML 不同,JSX 中所有标签都需要显式闭合。此规则也适用于自闭合标签(如 hr、br 和 img 标签)。
     3. **使用驼峰式命名**:
        建议在 JSX 中对属性使用驼峰式命名。例如,HTML 元素的常见属性如 `class`、`tabindex` 在 JSX 中应使用 `className` 和 `tabIndex`。  
        **注意:** `aria-*` 和 `data-*` 属性是例外,它们应始终使用小写。

**[⬆ Back to Top](#table-of-contents)**

249.  ### 为什么多个 JSX 标签需要被包裹?

      在幕后,JSX 会被转换为普通的 JavaScript 对象。如果不将多个对象包装到数组中,就无法从函数中返回它们。这就是为什么如果不将多个 JSX 标签包装到单个父标签或 Fragment 中,就不能直接从函数中返回它们的原因。

**[⬆ Back to Top](#table-of-contents)**

250.  ### 如何防止数组变量被修改(mutating)?

      函数作用域之外的预先存在的变量(包括 state、props 和 context)会导致修改,并在渲染阶段导致不可预测的错误。处理数组变量时应注意以下几点。

      1. 你需要获取原始数组的副本,并对其执行数组操作以进行渲染。这称为局部修改。
      2. 避免对原始数组触发修改方法,如 push、pop、sort 和 reverse 方法。使用 filter、map 和 slice 方法是安全的,因为它们会创建一个新数组。

**[⬆ Back to Top](#table-of-contents)**

251. ### 什么是捕获阶段事件(capture phase events)?

     React 的 `onClickCapture` 事件有助于捕获子元素的所有事件,无论事件传播逻辑如何,即使事件传播已停止。如果你需要为分析目的记录每个点击事件,这很有用。

     例如,以下代码会先触发父元素的点击事件,然后触发二级子元素的事件,即使叶子节点的按钮元素停止了传播。

     ```jsx
     <div onClickCapture={() => alert("parent")}>
       <div onClickCapture={() => alert("child")}>
         <button onClick={(e) => e.stopPropagation()} />
         <button onClick={(e) => e.stopPropagation()} />
       </div>
     </div>
     ```

     上述代码片段的事件传播按以下顺序发生:

     1. 它通过调用所有 `onClickCapture` 事件处理程序在 DOM 树中向下传播。
     2. 它在目标元素上执行 `onClick` 事件处理程序。
     3. 它通过调用上方所有 `onClick` 事件处理程序在 DOM 树中向上传播。

252. ### React 如何更新应用程序中的屏幕?

     React 通过三个步骤更新 UI:

     1. **触发或启动渲染**:组件将通过两种方式触发渲染。

        1. **初始渲染**:应用启动时,你可以通过调用 `creatRoot` 并传入目标 DOM 节点,然后调用组件的 `render` 方法来触发初始渲染。例如,以下代码片段将 `App` 组件渲染到根 DOM 节点上。

        ```jsx
        import { createRoot } from "react-dom/client";

        const root = createRoot(document.getElementById("root"));
        root.render(<App />);
        ```

        2. **状态更新时重新渲染**:当你使用状态设置函数更新组件状态时,组件的状态会自动排队等待渲染。

     2. **渲染组件**:触发渲染后,React 将调用你的组件以在屏幕上显示它们。对于初始渲染,React 会调用根组件;对于状态更新触发的渲染,React 会调用状态更新的函数组件。这对于目标组件的所有嵌套组件来说是一个递归过程。

     3. **将更改提交到 DOM**:调用组件后,React 会在初始渲染时使用 `appendChild()` DOM API 修改 DOM,并在重新渲染时根据重新渲染之间的差异应用最小必要的 DOM 更新。

**[⬆ Back to Top](#table-of-contents)**

253.  ### React 如何批处理多个状态更新?

      React 通过将事件处理程序中的多个状态更新分组,防止组件因每次状态更新而重新渲染。这种策略提高了应用程序性能,此过程称为**批处理**。旧版本的 React 仅支持对浏览器事件进行批处理,而 React 18 则支持对异步操作、超时、间隔以及原生事件进行批处理。这种改进的批处理版本称为**自动批处理**。

      让我们通过以下示例演示此自动批处理功能。

      ```jsx
      import { useState } from "react";

      export default function BatchingState() {
        const [count, setCount] = useState(0);
        const [message, setMessage] = useState("batching");

        console.log("Application Rendered");

        const handleAsyncFetch = () => {
          fetch("https://jsonplaceholder.typicode.com/users/1").then(() => {
            // 自动批处理仅重新渲染一次
            setCount(count + 1);
            setMessage("users fetched");
          });
        };

        return (
          <>
            <h1>{count}</h1>
            <button onClick={handleAsyncFetch}>Click Me!</button>
          </>
        );
      }
      ```

      前面的代码在一个事件处理程序中更新了两个状态变量。然而,React 会执行自动批处理功能,组件为了获得更好的性能只会重新渲染一次。

**[⬆ Back to Top](#table-of-contents)**

254.  ### 是否可以阻止自动批处理?

      是的,可以阻止自动批处理的默认行为。在某些情况下,你可能需要在每次状态更新后重新渲染组件,或者一个状态的更新依赖于另一个状态变量。考虑到这种情况,React 从 `react-dom` API 中引入了 `flushSync` 方法,用于需要立即将状态更新刷新到 DOM 的场景。

      在 `onClick` 事件处理程序中使用 `flushSync` 方法的示例如下:

      ```jsx
      import { flushSync } from "react-dom";

      const handleClick = () => {
        flushSync(() => {
          setClicked(!clicked); // 组件将在此处重新渲染
        });

        setCount(count + 1); // 组件将在此处再次重新渲染
      };
      ```

      在上述点击处理程序中,React 会首先通过 flushSync 更新 DOM,第二次由于计数器设置函数更新 DOM,从而避免了自动批处理。

**[⬆ Back to Top](#table-of-contents)**

255.  ### 什么是 React  hydration?

      React hydration 用于为服务器生成的预渲染静态 HTML 添加客户端 JavaScript 交互性。它仅用于服务器端渲染(SSR),以提高初始渲染时间并使应用程序对 SEO 友好。这种 hydration 充当了一座桥梁,缩小了服务器端渲染和客户端渲染之间的差距。

      在用生成的静态 HTML 加载页面后,React 会通过为各个元素附加所有事件处理程序来添加应用程序状态和交互性。让我们通过一个示例来演示这一点。

      假设 React DOM API(使用 `renderToString`)为包含用于递增计数器的 `<button>` 元素的 `<App>` 组件生成了 HTML。

      ```jsx
      import {useState} from 'react';
      import { renderToString } from 'react-dom/server';

      export default function App() {
        const [count, setCount] = React.useState(0);

        return (
          <h1>Counter</h1>
          <button onClick={() => setCount(prevCount => prevCount + 1)}>
            {count} times
          </button>
          );
      }

      const html = renderToString(<App />);
      ```

      上面的代码生成了以下 HTML,其中包含标题文本和按钮组件,但没有任何交互性。

      ```html
      <h1>Counter</h1>
      <button>
        <!-- -->0<!-- -->
        times
      </button>
      ```

      在这个阶段,可以使用 `hydrateRoot` API 执行 hydration,附加 `onClick` 事件处理程序。

      ```jsx
      import { hydrateRoot } from "react-dom/client";
      import App from "./App.js";

      hydrateRoot(document.getElementById("root"), <App />);
      ```

      完成此步骤后,你就能够在服务器端运行 React 应用程序,并在客户端 hydration JavaScript 包,以获得流畅的用户体验和 SEO 效果。

**[⬆ Back to Top](#table-of-contents)**

256.  ### 如何更新 state 中的对象?

      你不能直接更新 state 中存在的对象。相反,你应该创建一个全新的对象(或从现有对象复制),并使用新创建的对象更新最新的 state。尽管 JavaScript 对象是可变的,但在更新 state 时,你需要将 state 内的对象视为只读的。

      让我们通过一个示例来比较。常规对象突变方法的问题可以通过更新 `Profile` 组件的用户详细信息字段来描述。`Profile` 组件的属性,如 firstName、lastName 和 age 详细信息,在事件处理程序中被突变,如下所示。

      ```jsx
      import { useState } from "react";

      export default function Profile() {
        const [user, setUser] = useState({
          firstName: "John",
          lastName: "Abraham",
          age: 30,
        });

        function handleFirstNameChange(e) {
          user.firstName = e.target.value;
        }

        function handleLastNameChange(e) {
          user.lastName = e.target.value;
        }

        function handleAgeChange(e) {
          user.age = e.target.value;
        }

        return (
          <>
            <label>
              First name:
              <input value={user.firstName} onChange={handleFirstNameChange} />
            </label>
            <label>
              Last name:
              <input value={user.lastName} onChange={handleLastNameChange} />
            </label>
            <label>
              Age:
              <input value={user.age} onChange={handleAgeChange} />
            </label>
            <p>
              Profile:
              {person.firstName} {person.lastName} ({person.age})
            </p>
          </>
        );
      }
      ```

      一旦使用上述用户个人资料组件运行应用程序,你会发现输入字段时用户个人资料详细信息不会更新。
      可以通过创建对象的新副本来修复此问题,该副本通过扩展语法(...obj)包含现有属性,并在单个事件处理程序中添加更改的值,如下所示。

      ```jsx
      handleProfileChange(e) {
        setUser({
        ...user,
          [e.target.name]: e.target.value
        });
      }
      ```

      上面的事件处理程序非常简洁,无需为每个字段维护单独的事件处理程序。现在,UI 会按预期显示更新后的字段值,没有问题。

**[⬆ Back to Top](#table-of-contents)**

257.  ### 如何更新 state 中的嵌套对象?

      你不能简单地对 state 中的所有类型的对象都使用扩展语法。因为扩展语法是浅层的,它只复制一层深度的属性。如果对象具有嵌套对象结构,使用常规的 JavaScript 嵌套属性突变,UI 将无法按预期工作。让我们通过一个 User 对象的示例来演示这种行为,该对象内部有一个 address 嵌套对象。

      ```jsx
      const user = {
        name: "John",
        age: 32,
        address: {
          country: "Singapore",
          postalCode: 440004,
        },
      };
      ```

      如果你尝试以常规的 JavaScript 方式(如下所示)更新 country 嵌套字段,那么用户个人资料屏幕将不会更新为最新值。

      ```js
      user.address.country = "Germany";
      ```

      可以通过将所有字段展平到顶级对象中,或者为每个嵌套对象创建一个新对象并将其指向其父对象来解决此问题。在此示例中,首先需要创建 address 对象的副本,并使用最新值更新它。然后,address 对象应链接到父 user 对象,如下所示。

      ```js
      setUser({
        ...user,
        address: {
          ...user.address,
          country: "Germany",
        },
      });
      ```

      这种方法有点冗长,并且对于深层次的状态更新并不容易。但是,对于几层嵌套对象,这种解决方法可以使用,不会有太多麻烦。

**[⬆ Back to Top](#table-of-contents)**

258.  ### 如何更新 state 中的数组?

      尽管 JavaScript 中的数组本质上是可变的,但在将它们存储在 state 中时,你需要将它们视为不可变的。这意味着,与对象类似,数组不能直接在 state 中更新。相反,你需要创建现有数组的副本,然后将 state 设置为使用新复制的数组。

      为确保数组不被突变,应避免对原始数组执行直接赋值(arr[1]='one')、push、pop、shift、unshift、splice 等突变操作。相反,你可以借助 filter、map、slice、扩展语法等数组操作创建现有数组的副本。

      例如,下面的 push 操作不会在事件处理程序中将新的 todo 添加到总 todo 列表中。

      ```jsx
      onClick = {
        todos.push({
          id: id+1,
          name: name
        })
      }
      ```

      通过使用扩展语法替换 push 操作可以解决此问题,扩展语法将创建一个新数组,并且 UI 会使用新的 todo 进行更新。

      ```jsx
      onClick = {
        [
          ...todos,
          { id: id+1, name: name }
        ]
      }
      ```
**[⬆ Back to Top](#table-of-contents)**

259.  ### 如何使用 immer 库进行状态更新?

      Immer 库基于**写时复制**机制强制 state 的不可变性。它使用 JavaScript 代理来跟踪对不可变状态的更新。Immer 有以下 3 个主要状态:

      1.  **当前状态(Current state):** 指的是实际状态
      2.  **草稿状态(Draft state):** 所有新的更改都将应用于此状态。在此状态下,草稿只是当前状态的代理。
      3.  **下一个状态(Next state):** 在将所有突变应用于草稿状态后形成

      可以通过以下说明使用 Immer:

      1. 使用 `npm install use-immer` 命令安装依赖项
      2. 从顶部导入,将 `useState` 钩子替换为 `useImmer` 钩子
      3. `useImmer` 钩子的 setter 函数可用于更新状态。

      例如,immer 库的突变语法简化了 user 状态的嵌套 address 对象,如下所示:

      ```jsx
      import { useImmer } from "use-immer";
      const [user, setUser] = useImmer({
        name: "John",
        age: 32,
        address: {
          country: "Singapore",
          postalCode: 440004,
        },
      });

      // 在任何事件发生时更新用户详细信息
      setUser((draft) => {
        draft.address.country = "Germany";
      });
      ```

      前面的代码使你能够使用简洁的突变语法更新嵌套对象。

**[⬆ Back to Top](#table-of-contents)**

260.  ### 防止直接状态突变有什么好处?
**[⬆ Back to Top](#table-of-contents)**

261.  ### 更新状态时首选和非首选的数组操作有哪些?

      下表列出了更新组件状态时首选和非首选的数组操作。

      | 操作      | 首选方法              | 非首选方法                  |
      | --------- | --------------------- | --------------------------- |
      | 添加      | concat, [...arr]      | push, unshift               |
      | 删除      | filter, slice         | pop, shift, splice          |
      | 替换      | map                   | splice, arr[i] = someValue  |
      | 排序      | 复制到新数组          | reverse, sort               |

      如果你使用 Immer 库,那么你可以毫无问题地使用所有数组方法。

**[⬆ Back to Top](#table-of-contents)**

262. ### 定义嵌套函数组件会发生什么?
从技术上讲,可以编写嵌套函数组件,但不建议编写嵌套函数定义。因为这会导致意外的错误和性能问题。

**[⬆ Back to Top](#table-of-contents)**

263. ### 可以对非列表项使用 keys 吗?

     Keys 主要用于渲染列表项,但它们不仅仅用于列表项。你也可以使用它们让 React 区分组件。默认情况下,React 使用组件在

**[⬆ Back to Top](#table-of-contents)**

264. ### 编写 reducers 应遵循哪些准则?

     在代码中编写 reducers 时,需要注意两个准则。

     1. Reducers 必须是纯函数,不能突变状态。这意味着,相同的输入总是返回相同的输出。这些 reducers 与状态更新器函数类似,在渲染时运行。因此,这些函数不应发送任何请求、安排超时和任何其他副作用。

     2. 即使对数据应用了多个更改,每个 action 也应描述单个用户交互。例如,如果你“重置”包含许多用户输入字段的注册表单,该表单由 reducer 管理,建议发送一个“reset” action,而不是为每个字段创建单独的 action。action 的正确顺序应反映浏览器中的用户交互,这对调试非常有帮助。

**[⬆ Back to Top](#table-of-contents)**

265. ### ReactJS 在幕后是如何工作的?
     ReactJS 是一个强大的用于构建用户界面的 JavaScript 库。虽然表面上看起来简单,但 React 在幕后执行了许多复杂的操作,以高效地更新 UI。以下是其内部工作原理的概述:

     #### **1. 虚拟 DOM(Virtual DOM)和组件渲染**

     React 不直接操作真实 DOM。相反,它使用**虚拟 DOM** —— UI 的轻量级 JavaScript 表示。

     当组件渲染时(例如 `<App />`):

        *   React **执行组件函数**(例如 `App()`)。
        *   像 `useState` 这样的钩子按顺序注册和跟踪。
        *   React 从返回的 JSX 构建**虚拟 DOM 树**。
        *   这个虚拟 DOM 是一个**普通的 JS 对象**,描述了所需的 UI。

     此过程确保在 React 决定如何更新真实 DOM 之前进行快速高效的渲染。

     #### 2. **React Fiber 架构**

     React 的核心引擎称为**Fiber**,在 React 16 中引入。Fiber 是 React 协调算法的重新实现,具有以下功能:

        *   将渲染工作分解为**工作单元**(fiber 节点)。
        *   支持**可中断的渲染**(对响应性很重要)。
        *   支持**优先级调度**和**并发渲染**。

     每个 Fiber 节点代表一个组件,并存储:

        *   组件类型(函数/类)。
        *   Props、状态和副作用。
        *   指向父、子和兄弟 fiber 的链接。

     #### 3. **协调(Reconciliation)(差异算法)**

     当状态或 props 发生变化时:

        *   React 重新执行组件以生成新的虚拟 DOM。
        *   它使用高效的差异算法**将新的虚拟 DOM 与之前的虚拟 DOM 进行比较**。
        *   React 确定所需的**最小 DOM 更改集**。

     此过程称为**协调**。

     #### 4. **提交阶段(Commit Phase)(真实 DOM 更新)**

     一旦协调完成:

        *   React 进入**提交阶段**。
        *   它将计算出的更改应用于**真实 DOM**。
        *   它还运行副作用,如 `useEffect` 或 `useLayoutEffect`。

     这是 React 直接与浏览器 DOM 交互的唯一时间。

     #### 5. **Hooks 和状态管理**

     使用 Hooks(如 `useState`、`useEffect`):

        *   React 为每个组件保留一个**内部 hooks 列表**。
        *   Hooks 通过它们在函数中的顺序来识别。
        *   当状态更新发生时,React 重新渲染组件并以相同的顺序重新运行 hooks。

     #### 6. **React 调度器(Scheduler)**

     React 使用内部的**调度器**来控制更新的优先级:

        *   像点击和输入这样的紧急任务会立即处理。
        *   非紧急任务(如数据获取)可以延迟或暂停。
        *   这提高了响应性,并允许在并发模式下进行**时间切片**。

**[⬆ Back to Top](#table-of-contents)**

266. ### `useReducer` 与 `useState` 有何不同?
     `useState` 和 `useReducer` 钩子之间存在显著差异。
           
        | 特性                 | `useState`                          | `useReducer`                          |
        |----------------------|-------------------------------------|---------------------------------------|
        | 状态复杂度           | 简单(一个变量或扁平对象)          | 复杂、多部分或深度嵌套                |
        | 更新方式             | 直接(例如 `setState(x)`)          | 通过 action(例如 `dispatch({})`)    |
        | 更新逻辑             | 在组件内部                          | 在 reducer 函数中                     |
        | 可重用性和可测试性   | 可重用性较低                        | 高度可重用且可测试                    |

**[⬆ Back to Top](#table-of-contents)**

267. ### 什么是 useContext?使用 useContext 需要遵循哪些步骤?
     `useContext` 钩子是 React 的内置钩子,它允许你在函数组件内部访问上下文的值,而无需将其包装在 `<Context.Consumer>` 组件中。

     它帮助你**避免 prop drilling**(通过多个层级手动传递 props),允许组件访问共享数据,如主题、身份验证状态或用户偏好设置。

     useContext 的使用涉及三个主要步骤:
      #### **步骤 1:创建上下文**
    
        使用 `React.createContext()` 创建上下文对象。
    
        ```jsx
        import React, { createContext } from 'react'; 
    
        const ThemeContext = createContext(); // 默认值可选
        ```
    
      通常会导出此上下文,以便其他组件可以导入它。

      #### **步骤 2:提供上下文值**
    
        使用 `Context.Provider` 包装组件树(或其一部分),并传递 `value` prop。
    
        ```jsx
        function App() {
            return ( 
                <ThemeContext.Provider value="dark">
                   <MyComponent />
                </ThemeContext.Provider>
            ); 
        }
        ```
    
        现在,`<ThemeContext.Provider>` 内部的任何组件都可以访问上下文值。

        #### **步骤 3:使用** `**useContext**` **消费上下文**
    
        在**Provider 内部**的任何函数组件中,使用 `useContext` 钩子:
    
        ```jsx
        import { useContext } from 'react'; 
        function MyComponent() {
            const theme = useContext(ThemeContext); // theme = "dark"
            return <p>Current Theme: {theme}</p>; 
        }
        ```
**[⬆ Back to Top](#table-of-contents)**

268. ### useContext 钩子的用例有哪些?
     React 中的 `useContext` 钩子用于跨组件共享数据,而无需手动通过每个层级传递 props。以下是一些常见且有效的用例:

        1.  **主题定制**  
            `useContext` 可用于管理应用程序范围的主题,例如明暗模式,确保一致的样式并支持用户驱动的定制。
        2.  **本地化和国际化**  
            它通过向组件提供翻译后的字符串或特定于区域设置的内容来支持本地化,使应用程序适应不同地区的用户。
        3.  **用户身份验证和会话管理**  
            `useContext` 允许全局访问身份验证状态和用户数据。这支持组件的条件渲染,并帮助管理受保护的路由或特定于用户的 UI 元素。
        4.  **共享模态框或侧边栏可见性**  
            它非常适合管理共享 UI 组件(如模态框、抽屉或侧边栏)的可见性,尤其是当它们的状态需要从应用程序的各个部分控制时。
        5.  **与** `**useReducer**` **结合用于全局状态管理**  
            当与 `useReducer` 结合使用时,`useContext` 成为管理更复杂全局状态逻辑的强大工具。这种模式有助于维护更清晰、可扩展的状态逻辑,而无需引入 Redux 等外部库。
             下面列出了 useContext 的一些常见用例,

**[⬆ Back to Top](#table-of-contents)**

269.  ### 何时使用客户端组件和服务器组件?

      如果你清楚应用程序的哪些部分需要使用客户端组件,哪些部分需要使用服务器组件,就能高效地构建 nextjs 应用程序。以下列出了客户端组件和服务器组件的常见使用场景:

        **客户端组件:**
        1. 当你需要为页面添加交互性和事件监听器(如 onClick()、onChange() 等)时。
        2. 如果你需要使用状态和生命周期副作用,如 useState()、useReducer()、useEffect() 等。
        3. 如果需要使用仅浏览器可用的 API。
        4. 如果你需要实现依赖于状态、副作用或仅浏览器 API 的自定义钩子。
        5. 页面中存在 React 类组件。

        **服务器组件:**
        1. 如果组件逻辑涉及数据获取。
        2. 如果你需要直接访问后端资源。
        3. 当你需要将敏感信息(访问令牌、API 密钥等)保留在服务器上时。
        4. 如果你想减少客户端 JavaScript,并将大型依赖项放在服务器上。

**[⬆ 返回顶部](#table-of-contents)**

270. ### nextjs 中的页面路由器(Page Router)和应用程序路由器(App Router)有什么区别?

     Next.js 提供两种不同的路由系统:**页面路由器(Page Router)**(传统方式)和 **应用程序路由器(App Router)**(在 Next.js 13 中引入)。应用程序路由器基于 React 服务器组件构建,为现代 Web 应用程序提供了更强大的功能。

     它们之间的主要区别如下:

     | 特性 | 页面路由器 | 应用程序路由器 |
     |---------|-------------|------------|
     | 目录 | 使用 `pages/` 目录 | 使用 `app/` 目录 |
     | 路由 | 基于文件的路由,如 `pages/about.js` | 基于文件的路由,使用文件夹和特殊文件,如 `app/about/page.js` |
     | 组件 | 默认所有组件都是客户端组件 | 默认所有组件都是服务器组件 |
     | 布局 | 自定义 `_app.js` 和 `_document.js` 用于共享布局 | 使用 `layout.js` 文件实现原生嵌套布局 |
     | 数据获取 | 使用 `getServerSideProps`、`getStaticProps` 和 `getInitialProps` | 在服务器组件中使用 `async/await` 和原生 `fetch` |
     | 加载状态 | 需要手动实现 | 内置 `loading.js` 用于流式处理和 Suspense |
     | 错误处理 | 自定义 `_error.js` 页面 | 内置 `error.js`,可在任何级别设置错误边界 |
     | 流式处理 | 支持有限 | 内置对 Suspense 流式处理的支持 |
     | 服务器操作 | 不可用 | 原生支持服务器端突变 |
     | 元数据 | 使用 `next/head` 中的 `Head` 组件 | 原生元数据 API,使用 `metadata` 对象或 `generateMetadata` 函数 |
     | 渲染方式 | SSR、SSG、ISR 和 CSR | SSR、SSG、ISR、CSR 以及 React 服务器组件 |

     **页面路由器结构示例:**
     ```
     pages/
     ├── index.js          // 首页 (/)
     ├── about.js          // 关于页 (/about)
     ├── _app.js           // 自定义 App 组件
     ├── _document.js      // 自定义 Document
     └── posts/
         └── [id].js       // 动态路由 (/posts/:id)
     ```

     **应用程序路由器结构示例:**
     ```
     app/
     ├── page.js           // 首页 (/)
     ├── layout.js         // 根布局
     ├── loading.js        // 加载 UI
     ├── error.js          // 错误 UI
     ├── about/
     │   └── page.js       // 关于页 (/about)
     └── posts/
         └── [id]/
             └── page.js   // 动态路由 (/posts/:id)
     ```

     **注意:** 建议新的 Next.js 应用程序使用应用程序路由器,因为它提供了更好的性能、更简单的数据获取模式,以及借助 React 服务器组件提升的开发体验。

**[⬆ 返回顶部](#table-of-contents)**

271. ### 你能描述一下 useMemo() Hook 吗?

     React 中的 `useMemo()` Hook 用于通过**记忆化昂贵计算的结果**来**优化性能**。它确保函数**仅在其依赖项发生变化时才会重新执行**,从而避免在每次重新渲染时进行不必要的计算。

     #### 语法

     ```js
      const memoizedValue = useMemo(() => computeExpensiveValue(arg), [dependencies]);
     ```
      - **`computeExpensiveValue`**:  
      一个返回计算结果的函数。

     - **`dependencies`**:  
      一个值数组,当这些值发生变化时,会导致记忆化函数重新运行。

      如果自上次渲染以来依赖项没有变化,React 将返回**缓存的结果**,而不是重新运行函数。

      让我们通过用户搜索及其相应的筛选用户列表示例来解释 `useMemo` 钩子的用法。

      #### 示例:记忆化筛选列表
      ```javascript
      import React, { useState, useMemo } from 'react';

      const users = [
        { id: 1, name: 'Sudheer' },
        { id: 2, name: 'Brendon' },
        { id: 3, name: 'Charlie' },
        { id: 4, name: 'Dary' },
        { id: 5, name: 'Eden' }
      ];

      export default function UserSearch({ users }) {
        const [searchTerm, setSearchTerm] = useState('');
        const [counter, setCounter] = useState(0);

        // 基于搜索词记忆化筛选后的用户列表
        const filteredUsers = useMemo(() => {
          console.log("Filtering users...");
          return users.filter(user =>
            user.name.toLowerCase().includes(searchTerm.toLowerCase())
          );
        }, [searchTerm]);

        return (
          <div>
            <h2>Counter: {counter}</h2>
            <button onClick={() => setCounter(prev => prev + 1)}>Increment Counter</button>

            <h2>Search Users</h2>
            <input
              type="text"
              value={searchTerm}
              onChange={(e) => setSearchTerm(e.target.value)}
              placeholder="Enter name"
            />

            <ul>
              {filteredUsers.map(user => (
                <li key={user.id}>{user.name}</li>
              ))}
            </ul>
          </div>
        );
      }
      ```

      在上面的示例中:
        - 仅当 searchTerm 变化时,才会重新计算 filteredUsers 列表。
        - 点击“Increment Counter”按钮不会再次触发筛选逻辑,因为它不是依赖项。
        - 只有当搜索词更新时,控制台才会记录“Filtering users...”。

**[⬆ 返回顶部](#table-of-contents)**

272. ### Hooks 可以在类组件中使用吗?
     不,Hooks 不能在类组件内部使用。它们是专门为函数组件设计的。这是因为 Hooks 依赖于在组件渲染期间调用它们的顺序,而这种顺序只在函数组件中得到保证。不过,类组件和函数组件可以在同一个应用程序中共存。

**[⬆ 返回顶部](#table-of-contents)**

273. ### 什么是更新器函数(updater function)?是否在所有情况下都应该使用更新器函数?
     **更新器函数**是 `setState` 的一种形式,你传递一个**函数**而不是直接的值。这个函数接收**前一个状态**作为参数,并返回**下一个状态**。
     
     更新器函数的表达式如下所示:
     ```js
     setCount(prevCount => prevCount + 1); // 安全且可预测
     ```
     这里,`prevCount => prevCount + 1` 就是更新器函数。

     在 React 社区中,通常建议在更新依赖于先前值的状态时使用更新器函数。这有助于防止因使用过时或“陈旧”状态而可能出现的意外行为。

     虽然使用更新器函数是一个好习惯,但并非总是必要的。在大多数情况下,React 会批处理更新,并确保状态在事件处理程序开始时是最新的,因此在单个同步事件期间通常不会遇到陈旧状态问题。
     但是,如果你在单个处理程序中对同一个状态变量进行多次更新,使用更新器形式可以确保每次更新都正确使用最新的状态值,而不是可能过时的值。

     **示例:在一个处理程序中进行多次更新**
     ```js
     function handleCount() {
        setCounter(a => a + 1);
        setCounter(a => a + 1);
        setCounter(a => a + 1);
     }
     ```

     在这个示例中,`a => a + 1` 是一个**更新器函数**。React 会将这些更新器函数排队,并按顺序应用它们,每个函数都使用最新的状态值。因此,计数器将正确地增加 3。

     在许多情况下,例如基于用户输入设置状态或分配静态值时,你不需要更新器函数:
     ```js
     setName('Sudheer');
     ```
**[⬆ 返回顶部](#table-of-contents)**

274. ### useState 可以接受函数作为初始值吗?
     是的,`useState` 可以接受函数作为初始值,这是 React 中的一个有用特性,称为**惰性初始化**。这个函数也被称为**初始化函数**。

     当你调用 useState(initialValue) 时,通常直接传入一个值:
     
     ```js
     const [count, setCount] = useState(0);  // 初始值为 0
     ```

     但是如果计算初始值的过程很昂贵或涉及逻辑,你可以传入一个返回该值的函数:
     ```js
        const [count, setCount] = useState(() => {
        // 此函数仅运行一次 —— 在组件首次渲染时
        return expensiveComputation();
        });
     ```
     
     这个函数避免了在每次渲染时都进行繁重的计算。如果你不使用这种函数形式,而是直接调用它,该函数将在组件每次渲染时运行,从而影响性能。
     例如,以下用法不推荐:
     ```js
     const [count, setCount] = useState(expensiveComputation());
     ```
**[⬆ 返回顶部](#table-of-contents)**

275. ### `useState` 可以保存哪些类型的值?

     `useState` 钩子可以接受不同类型的值。

     *   基本类型:`number`、`string`、`boolean`
     *   数组
     *   对象
     *   函数
     *   `null` 或 `undefined`

     但对于**引用类型(对象/数组)** 你需要小心,因为 React 通过**引用**比较旧值和新值,所以直接修改不会触发重新渲染。
     例如,状态更新的正确和错误方式如下所示:
     ```js
     user.name = "Sudheer"; //错误方式
     setUser(prev => ({ ...prev, name: 'Sudheer' })); //正确方式
     ```
**[⬆ 返回顶部](#table-of-contents)**

276. ### 如果有条件地调用 `useState` 会发生什么?
     根据 React Hooks 的规则,必须无条件地调用 Hooks。例如,如果你有条件地调用它:
     ```js
     if (someCondition) { 
        const [state, setState] = useState(0); 
     }
     ```

     React 会抛出运行时错误,因为它**依赖于 Hook 调用的顺序**,而条件逻辑会破坏这种顺序。

**[⬆ 返回顶部](#table-of-contents)**

277. ### useState 是同步的还是异步的?
     `useState` 钩子本身是同步的,但状态更新是异步的。当你调用 `useState()` 时,它会同步运行并返回状态变量和设置函数作为元组。
     ```js
     const [count, setCount] = useState(0);
     ```
     这在渲染期间立即发生。
     然而,状态更新函数(**setState**)在某种意义上是异步的,因为它不会立即更新状态。
     React 会**批处理**更新,并在下次渲染前应用它们。在调用 `setState` 后,你不会立即看到更新后的值。
     
     **示例:**
     ```js
     const [count, setCount] = useState(0);
    
     function handleClick() {
       setCount(count + 1);
       console.log(count); // ❗️仍然记录旧值
     }
     ```
     `console.log(count)` 打印**旧值**,因为更新尚未发生。
     
     要查看更新后的状态值,你可以使用 `useEffect()` 钩子。它在**组件重新渲染后**运行。当 `useEffect` 运行时:

        *   组件已经更新。
        *   **状态包含新值**。
     
        ```js
        import React, { useState, useEffect } from 'react';

        function Counter() {
        const [count, setCount] = useState(0);
        
        const handleClick = () => {
        setCount(count + 1);
        console.log('Clicked count (old):', count); // 旧值
        };
        
        useEffect(() => {
        console.log('Updated count:', count); // 新值
        }, [count]); // 仅在 `count` 变化时运行
        
        return <button onClick={handleClick}>Count: {count}</button>;
        }
        ```
**[⬆ 返回顶部](#table-of-contents)**

278.  ### 你能解释 useState 在内部是如何工作的吗?
      React 的钩子,包括 `useState`,依赖于一些内部机制,这些机制在渲染期间跟踪**每个组件**和**每个钩子调用**的状态。以下是内部机制的简化解释:

      #### 1. **钩子列表 / 链表**

      *   React 为每个组件维护一个“钩子状态”的链表或数组。
      *   当组件渲染时,React 通过一个游标/索引跟踪当前正在处理的钩子。
      *   每次对 `useState()` 的调用对应于此列表中的一个“槽位”。

      #### 2. **状态存储**

      *   每个槽位存储:
        *   当前状态值。
        *   待处理状态更新的队列。

      #### 3. **初始渲染**
        
      *   当组件首次渲染时,React:
         *   为 `useState` 创建一个新槽位,初始化为初始状态(例如 `0`)。
         *   返回 `[state, updaterFunction]`。

      #### 4. **更新器函数**
    
      *   更新器函数(`setCount`)是一个闭包,当被调用时:
        *   将状态更新加入 React 的内部队列。
        *   安排组件的重新渲染。
    
      #### 5. **重新渲染和状态更新**
    
      *   在下次渲染时:
        *   React 处理每个钩子槽位的所有排队更新。
        *   相应地更新存储的状态值。
        *   将新状态返回给组件。

      #### 6. **重要:钩子顺序**

      *   钩子必须在每次渲染时以相同的顺序调用,这样 React 才能将钩子调用与其内部槽位匹配。
      *   这就是为什么不能有条件地调用钩子。
      
      `useState` 内部实现的伪代码如下所示:
      ```js
        let hookIndex = 0;
        const hooks = [];
        
        function useState(initialValue) {
            const currentIndex = hookIndex;
        
            if (!hooks[currentIndex]) {
                // 首次渲染:初始化状态
                hooks[currentIndex] = {
                    state: initialValue,
                    queue: [],
                };
            }
        
            const hook = hooks[currentIndex];
        
            // 处理排队的更新
            hook.queue.forEach(update => {
                hook.state = update(hook.state);
            });
            hook.queue = [];
        
            // 定义更新器函数
            function setState(action) {
                // action 可以是新状态或 function(state) => new state
                hook.queue.push(typeof action === 'function' ? action : () => action);
                scheduleRender(); // 触发 React 重新渲染
            }
        
            hookIndex++;
            return [hook.state, setState];
        }
        ```
**[⬆ 返回顶部](#table-of-contents)**

279. ### 什么是 `useReducer`?为什么使用 useReducer?
     `useReducer` 钩子是 React 中用于在**函数组件**内部管理**复杂状态逻辑**的钩子。它在概念上类似于**Redux**。也就是说,不像使用 `useState` 那样直接更新状态,你而是**向一个**reducer 函数**派发一个动作(action),然后 reducer 返回新的状态。

     `useReducer` 钩子接受三个参数:

        ```js
        const [state, dispatch] = useReducer(reducer, initialState, initFunction);
        ```

        *   `**reducer**`:一个函数 `(state, action) => newState`,用于处理状态如何根据动作进行更改。
        *   `**initialState**`:初始状态。
        *   `**dispatch**`:一个你调用的函数,通过传递一个动作来触发更新。

     `useReducer` 钩子在以下情况时使用:

     *   **状态复杂**,例如嵌套结构或多个相关值。
     *   状态更新依赖于**先前的状态**和**逻辑**。
     *   你希望将**状态更新逻辑**与 UI 代码分离,使其更清晰且可测试。
     *   你正在管理以下功能:
         *   表单
         *   向导 / 多步骤流程
         *   撤销/重做功能
         *   购物车逻辑
         *   切换和条件 UI 逻辑

**[⬆ 返回顶部](#table-of-contents)**

280. ### `useReducer` 是如何工作的?用示例解释
     `useReducer` 钩子的工作方式类似于 Redux,其中:

        *   你定义一个**reducer 函数**来处理状态转换。
        *   你派发动作来更新状态。
     
     **带有增量、减量和重置功能的计数器示例:**
     1. Reducer 函数:

        定义一个计数器 reducer 函数,它接受当前状态和一个带有 type 的动作对象,并根据该 type 返回新的状态。
     
         ```js
         function counterReducer(state, action) {
            switch (action.type) {
              case 'increment':
                return { count: state.count + 1 };
              case 'decrement':
                return { count: state.count - 1 };
              case 'reset':
                return { count: 0 };
              default:
                return state;
            }
          }
         ```
     2. 使用 `useReducer`:
      调用 `useReducer`,传入上述 reducer 函数以及初始状态。此后,你可以为相应的按钮处理程序附加派发动作。
      ```js
      import React, { useReducer } from 'react';

        function Counter() {
          const initialState = { count: 0 };
          const [state, dispatch] = useReducer(counterReducer, initialState);

          return (
            <div style={{ textAlign: 'center' }}>
              <h2>Count: {state.count}</h2>
              <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
              <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
              <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
            </div>
          );
        }

      export default Counter;
      ```
      一旦返回新状态,React 就会使用更新后的 `state.count` 重新渲染组件。

**[⬆ 返回顶部](#table-of-contents)**

281. ### 你能将 **useReducer** 与 **useContext** 结合使用吗?

      是的,将 **useReducer** 与 **useContext** 结合使用来构建轻量级的状态管理系统(类似于 Redux)是很常见的:

      ```js
      const AppContext = React.createContext();

      function AppProvider({ children }) {
        const [state, dispatch] = useReducer(reducer, initialState);
        return (
          <AppContext.Provider value={{ state, dispatch }}>
            {children}
          </AppContext.Provider>
        );
      }
      ```
**[⬆ 返回顶部](#table-of-contents)**

282. ### 能否使用 useReducer 连续分发多个 action?
     可以使用 `useReducer` 连续分发多个 action,但不能在一次调用中直接完成。你需要多次调用 dispatch,或者在 reducer 中创建一个复合 action,根据 action 类型执行多个更新。
     
     **示例:分发多个 Action**
     你可以定义一个自定义函数,逐个分发 action。
     ```js
     function handleMultipleActions(dispatch) {
       dispatch({ type: 'increment' });
       dispatch({ type: 'increment' });
       dispatch({ type: 'reset' });
     }
     ```
     之后,需要通过事件处理程序调用它
     ```js
     <button onClick={() => handleMultipleActions(dispatch)}>
       执行多个 Action
     </button>
     ```
     **注意:** 如果你希望在一次 reducer 调用中处理多个状态变更,也可以定义一个自定义 action 类型。
     ```js
     case 'increment_twice':
       return { count: state.count + 2 };
     ```
     然后分发
     ```js
     dispatch({ type: 'increment_twice' });
     ```
**[⬆ Back to Top](#table-of-contents)**

283. ### useReducer 的 dispatch 是异步的吗?它会立即更新状态吗?

     `useReducer` 返回的 `dispatch` 函数**不是异步的**——它是一个**同步**函数调用。当你调用 `dispatch(action)` 时,React 会**同步**地使用当前状态和 action 调用 reducer,计算出新状态,并**安排重新渲染**。然而,在同一个渲染周期内,**状态变量不会立即更新**。更新后的状态只有在**下一次渲染**时才可用。

     这种行为与 `useState` 的 `setState` 类似——React 会**批处理**状态更新以优化性能,这意味着组件不会在每次 `dispatch` 调用后立即重新渲染。相反,React 会处理所有已分发的 action,并使用最终状态重新渲染一次。

     #### 关键点

     1. **`dispatch` 是同步的**:调用 `dispatch` 时,reducer 会立即运行。
     2. **当前渲染中状态不会立即更新**:状态变量在下次渲染前仍保持旧值。
     3. **React 批处理更新**:同一事件处理程序中的多个 `dispatch` 调用会导致单次重新渲染。
     4. **Reducer 是纯函数**:它计算新状态时不会产生副作用。

     #### 演示状态不会立即更新的示例

     ```jsx
     import React, { useReducer } from 'react';

     function reducer(state, action) {
       switch (action.type) {
         case 'increment':
           return { count: state.count + 1 };
         default:
           return state;
       }
     }

     function Counter() {
       const [state, dispatch] = useReducer(reducer, { count: 0 });

       const handleClick = () => {
         dispatch({ type: 'increment' });
         console.log(state.count); // 仍然输出旧值(例如 0),而不是 1
         dispatch({ type: 'increment' });
         console.log(state.count); // 仍然输出旧值(例如 0),而不是 2
       };

       // 重新渲染后,state.count 将变为 2(两个 dispatch 都已处理)
       return (
         <div>
           <p>Count: {state.count}</p>
           <button onClick={handleClick}>Increment Twice</button>
         </div>
       );
     }
     ```

     在上面的示例中,即使调用了两次 `dispatch`,事件处理程序内部的 `state.count` 仍然反映**之前**的值。React 会批处理这两个 dispatch,并使用 `count: 2` **单次**重新渲染组件。

     #### 如何在 dispatch 后读取更新的状态

     如果你需要在分发后立即获取更新后的值,有以下几种选择:

     1. **使用 `useEffect` 响应状态变化**:
        ```jsx
        useEffect(() => {
          console.log('Updated count:', state.count);
        }, [state.count]);
        ```

     2. **手动计算下一个状态**:
        ```jsx
        const handleClick = () => {
          const nextState = reducer(state, { type: 'increment' });
          console.log('Next state will be:', nextState.count);
          dispatch({ type: 'increment' });
        };
        ```

     3. **使用 `useRef` 跟踪最新状态**:
        ```jsx
        const stateRef = useRef(state);
        useEffect(() => {
          stateRef.current = state;
        }, [state]);
        ```

     **注意:** 这是 React 的设计行为。`dispatch` 函数本身具有**稳定的标识**(它不会在重新渲染之间变化),因此可以安全地从 `useEffect` 的依赖数组中省略。

**[⬆ Back to Top](#table-of-contents)**

284. ### useContext 是如何工作的?请举例说明
     `useContext` hook 可用于在 React 应用中跨多个组件和页面管理身份验证状态。
    
     让我们构建一个简单的身份验证流程,包含:

        *   **登录和登出按钮**
        *   用于共享状态的全局 `AuthContext`
        *   可以**访问和更新**身份验证状态的组件
     
      **1. 创建 Auth Context:**

       你可以定义 `AuthProvider`,它通过 context 持有并提供 `user`、`login()` 和 `logout()`。
        ```js
        // AuthContext.js
        import React, { createContext, useContext, useState } from 'react';
        
        const AuthContext = createContext();
        
        export function AuthProvider({ children }) {
          const [user, setUser] = useState(null);
        
          const login = (username) => setUser({ name: username });
          const logout = () => setUser(null);
        
          return (
            <AuthContext.Provider value={{ user, login, logout }}>
              {children}
            </AuthContext.Provider>
          );
        }
        
        // 用于更简洁使用的自定义 hook
        export const useAuth = () => useContext(AuthContext);
        ```
     **2. 用 Provider 包装应用:**
       
        在主 `App.js` 文件中包装上面创建的 provider     
        ```js
        // App.js
        import React from 'react';
        import { AuthProvider } from './AuthContext';
        import HomePage from './HomePage';
        import Dashboard from './Dashboard';
        
        function App() {
          return (
            <AuthProvider>
              <HomePage />
              <Dashboard />
            </AuthProvider>
          );
        }
        
        export default App;
        ```
     **3. 带有登录功能的主页:**
      通过自定义 useAuth hook 读取或访问用户和登录详情,并在主页中使用。

        ```js
         // HomePage.js
         import React from 'react';
         import { useAuth } from './AuthContext';
    
         function HomePage() {
           const { user, login } = useAuth();
    
           return (
             <div>
               <h1>首页</h1>
               {user ? (
                 <p>欢迎回来,{user.name}!</p>
               ) : (
                 <button onClick={() => login('Alice')}>登录</button>
               )}
             </div>
           );
         }
    
         export default HomePage;
        ```
     
     **4. 带有登出功能的仪表板:**
      从 `useAuth` 自定义 hook 读取或访问用户和登出详情,并在仪表板页面中使用。

      ```js
        // Dashboard.js
        import React from 'react';
        import { useAuth } from './AuthContext';
        
        function Dashboard() {
          const { user, logout } = useAuth();
        
          if (!user) {
            return <p>请登录以查看仪表板。</p>;
          }
        
          return (
            <div>
              <h2>仪表板</h2>
              <p>当前登录用户:{user.name}</p>
              <button onClick={logout}>登出</button>
            </div>
          );
        }
        
        export default Dashboard;
      ```
**[⬆ Back to Top](#table-of-contents)**

285. ### 能否在一个组件中使用多个 Context?
     可以。你可以在同一个组件中多次调用 useContext,为每个 context 调用一次。

     可以通过以下步骤实现:

        *   使用 `createContext()` 创建多个 context。
        *   用多个 `<Provider>` 包装组件树。
        *   在同一个组件中为每个 context 分别调用 `useContext()`。
     
     **示例:同时使用 `ThemeContext` 和 `UserContext`**
     ```js
     import React, { createContext, useContext } from 'react';

      // 步骤 1:创建两个 context
      const ThemeContext = createContext();
      const UserContext = createContext();

      function Dashboard() {
        // 步骤 2:使用两个 context
        const theme = useContext(ThemeContext);
        const user = useContext(UserContext);

        return (
          <div style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
            <h1>欢迎,{user.name}</h1>
            <p>当前主题:{theme}</p>
          </div>
        );
      }

      // 步骤 3:提供两个 context
      function App() {
        return (
          <ThemeContext.Provider value="dark">
            <UserContext.Provider value={{ name: 'Sudheer' }}>
              <Dashboard />
            </UserContext.Provider>
          </ThemeContext.Provider>
        );
      }

      export default App;
     ```
**[⬆ Back to Top](#table-of-contents)**

286. ### 使用 useContext 处理对象时,常见的陷阱是什么?
     使用 `useContext` 处理对象时,一个**常见陷阱**是**触发所有消费组件的不必要重新渲染**——即使只有 context 值的一部分发生了变化。

     当你提供一个对象作为 context 值时,React 会比较整个对象引用。如果对象发生变化(即使是轻微变化),React 会认为整个 context 都已更改,**所有使用** `useContext(MyContext)` 的组件**都会重新渲染**,无论它们是否使用了发生变化的部分。

     **示例:**
     ```js
        const MyContext = React.createContext();

        function MyProvider({ children }) {
          const [user, setUser] = useState(null);
          const [theme, setTheme] = useState('light');

          // 这会导致所有消费者在任何状态变化时重新渲染
          const contextValue = { user, setUser, theme, setTheme };

          return (
            <MyContext.Provider value={contextValue}>
              {children}
            </MyContext.Provider>
          );
        }
     ```
     在这种情况下,`theme` 的变化也会触发仅关心 `user` 的组件重新渲染。

     这个问题可以通过两种方式解决:

     **1. 拆分 Context**  
     为不相关的状态片段创建单独的 context:

     ```jsx
     const UserContext = React.createContext(); 
     const ThemeContext = React.createContext();
     ```

     **2. 记忆化 Context 值**  
     使用 `useMemo` 防止不必要的重新渲染:

     ```jsx
       const contextValue = useMemo(() => ({ user, setUser, theme, setTheme }), [user, theme]);
     ```

     然而,这只有在对象结构和依赖项得到良好控制的情况下才有效。

**[⬆ Back to Top](#table-of-contents)**

287. ### 没有匹配的 provider 时,context 值会是什么?
    
     当组件调用 `useContext(SomeContext)` 但组件树中**没有更高层级的** `<SomeContext.Provider>` 时,将返回传递给 `React.createContext(defaultValue)` 的**默认值**。
    
     ```js
     const ThemeContext = React.createContext('light'); // 'light' 是默认值
    
     function ThemedComponent() {
       const theme = useContext(ThemeContext);
       return <div>当前主题:{theme}</div>;
     }
    
     // 树中没有 ThemeContext.Provider
     ```
     在这种情况下,`theme` 将是 'light'。这是你创建 context 时提供的默认值。
    
     **注意:** 如果你没有指定默认值,当没有 provider 时,context 值将是 undefined:
    
     ```jsx
     const AuthContext = React.createContext(); // 没有默认值
    
     function Profile() {
       const auth = useContext(AuthContext);
       // 如果没有 AuthContext.Provider,auth 将是 undefined
     }
     ```
**[⬆ Back to Top](#table-of-contents)**

288. ### useEffect 依赖数组中的响应式依赖如何影响其执行行为?
     `useEffect` hook 接受一个可选的依赖项参数,该参数接受一个响应式值数组。**依赖数组**决定了**何时**运行 effect。也就是说,它使 `useEffect` 对指定值的变化具有_响应性_。

     #### **依赖数组如何影响行为**

     1. **空依赖数组:`**[]**`**

     ```css
     useEffect(() => {
       // 在初始渲染后运行一次
     }, []);
     ```

      *   Effect **仅运行一次**(类似于 `componentDidMount`)。
      *   忽略所有状态/属性变化。

     2. **带有特定依赖项:`**[count, user]**`**

     ```css
     useEffect(() => {
       // 在初始渲染后运行
       // 并且每当 `count` 或 `user` 变化时运行
     }, [count, user]);
     ```

       *   Effect 在**首次渲染**时运行,并且
       *   每当**任何依赖项值发生变化**时再次运行。

     3. **没有依赖数组(省略)**

          ```css
          useEffect(() => {
            // 在**每次**渲染后运行
          });
          ```

           *   Effect 在**每次渲染**后运行,无论发生了什么变化。
           *   如果使用不当,可能会导致**性能问题**。

       React 对依赖项进行浅比较。如果任何值发生变化(!==),effect 将重新运行。
       
       **注意:** 当依赖项是基本类型或记忆化的对象/函数时,此 hook 工作良好。

**[⬆ Back to Top](#table-of-contents)**

289. ### React 何时以及多久调用一次 useEffect hook 内部的 setup 和 cleanup 函数?

     1. **Setup 函数执行(`useEffect`)**

         你传递给 `useEffect` 的 setup 函数(或主函数)在特定时间点运行:

           1.  **组件挂载后**(如果依赖数组为空 `[]`)
           2.  **每次渲染后**(如果未提供依赖数组)
           3.  **依赖项值变化后**(如果依赖数组包含变量)

     2. **Cleanup 函数执行(从 `useEffect` 返回的函数)**

        Cleanup 函数在**effect 重新执行前**和组件**卸载时**被调用。

**[⬆ Back to Top](#table-of-contents)**

290. ### 从 useEffect 返回 Promise 会发生什么?
      不应该从 useEffect 返回 Promise。React 期望传递给 useEffect 的函数要么不返回任何内容(undefined),要么返回一个 cleanup 函数(同步函数)。也就是说,它不期望也不处理返回的 Promise。如果你仍然返回 Promise,React 会默默地忽略它,并且在严格模式下可能会导致错误或警告。

      **错误示例:**
      ```js
      useEffect(async () => {
        await fetchData(); // ❌ useEffect 不应该是 async 的
      }, []);
      ```
      **正确示例:**
      ```jsx
      useEffect(() => {
        const fetchData = async () => {
          const res = await fetch('/api');
          const data = await res.json();
          setData(data);
        };

        fetchData();
      }, []);
      ```
**[⬆ Back to Top](#table-of-contents)**

290. ### 能否在单个组件中使用多个 useEffect hook?
      可以,当你想要分离关注点时,允许多个 useEffect hook 并且推荐这样做。

      ```jsx
      useEffect(() => {
        // 处理 API 获取
      }, []);

      useEffect(() => {
        // 处理事件监听器
      }, []);
      ```
      每个 effect 独立运行,有助于使代码模块化且更易于调试。

**[⬆ Back to Top](#table-of-contents)**

291. ### 如何防止 useEffect 出现无限循环?
        当 effect 更新了其依赖数组中列出的状态时,就会发生无限循环,这会导致 effect 重新运行,再次更新状态,依此类推。
        
        **无限循环场景:**
        ```js
        useEffect(() => {
          setCount(count + 1);
        }, [count]); // 每次 count 更新时再次触发
        ```
        你需要确保 setState 调用不依赖于导致 effect 重新运行的值,或者使用条件语句隔离它们。
        ```js
        useEffect(() => {
          if (count < 5) {
            setCount(count + 1);
          }
        }, [count]);
        ```
**[⬆ Back to Top](#table-of-contents)**

292. ### useLayoutEffect 的使用场景是什么?
      当你的 effect **必须在浏览器绘制之前运行**时,需要使用 `useLayoutEffect`,例如:

      *   **读取布局测量值**(如元素大小、滚动位置)
      *   **同步应用 DOM 样式**以防止视觉闪烁
      *   **布局或过渡动画**
      *   **与需要 DOM 操作的第三方库集成**

      如果没有视觉或布局依赖,优先使用 `useEffect`——它对性能更友好。

      ```js
      useLayoutEffect(() => {
        const width = divRef.current.offsetWidth;
        if (width < 400) {
          divRef.current.style.background = 'blue'; // 防止闪烁
        }
      }, []);
      ```
**[⬆ Back to Top](#table-of-contents)**

293. ### useLayoutEffect 在服务器端渲染 (SSR) 期间如何工作?

     `useLayoutEffect` hook **不会在服务器上运行**,因为服务器没有 DOM。如果在 Next.js 等服务器环境中直接使用 `useLayoutEffect`,React 会发出警告。

     这可以通过条件 polyfill 来缓解:

      ```jsx
      const useIsomorphicLayoutEffect =
        typeof window !== 'undefined' ? useLayoutEffect : useEffect;
      ```

      也就是说,在同时在客户端和服务器渲染的组件中使用 `useIsomorphicLayoutEffect`。     

**[⬆ Back to Top](#table-of-contents)** 

294. ### 如果将 useLayoutEffect 用于非布局逻辑会发生什么?
      将 `useLayoutEffect` 用于**与布局或视觉 DOM 变化无关的逻辑**(如日志记录、数据获取或分析)是**不推荐的**。这可能会导致**性能问题**甚至意外行为。

      **示例:反模式**
      ```js
      useLayoutEffect(() => {
        console.log("跟踪分析数据");
        fetch('/log-page-view');
      }, []);
      ```
      上述用法只是为了发送网络请求而延迟了 UI 的绘制,这(并且应该)可以使用 useEffect 在绘制后完成。

**[⬆ Back to Top](#table-of-contents)** 

295. ### useLayoutEffect 如何导致布局抖动(layout thrashing)?
      当你**反复读写 DOM** 并强制浏览器每帧多次重新计算布局时,`useLayoutEffect` 可能会**导致布局抖动**。这是因为 `useLayoutEffect` 在_浏览器绘制之前_运行,这些回流是_同步_发生的,会阻塞渲染并降低性能。

      **示例:**
      ```js
      function ThrashingComponent() {
        const ref = useRef();

        useLayoutEffect(() => {
          const height = ref.current.offsetHeight; // 读取
          ref.current.style.height = height + 20 + 'px'; // 写入
          const newHeight = ref.current.offsetHeight; // 再次读取 — 强制回流
        }, []);

        return <div ref={ref}>Hello</div>;
      }
      ```
      在上面的代码中,每次读写循环都会触发同步回流,阻塞主线程并延迟 UI 渲染。

      可以通过批处理 DOM 读写操作并避免在写入后进行不必要的读取来避免此问题。

**[⬆ Back to Top](#table-of-contents)**

**[⬆ 返回顶部](#table-of-contents)**

296. ### 如何在 React 中使用 useRef 访问 DOM 元素?请举例说明。
        `useRef` 钩子在 React 中常用于直接引用和操作 DOM 元素,例如聚焦输入框、滚动到某个区域或控制媒体元素。
        
        当使用 useRef 为 DOM 元素分配 ref 时,React 允许你通过 ref 对象的 `.current` 属性访问底层 DOM 节点。
        
        **示例:聚焦输入框**

        ```js
        import React, { useRef } from 'react';
        
        function FocusInput() {
          const inputRef = useRef(null); // 创建 ref
        
          const handleFocus = () => {
            inputRef.current.focus(); // 访问 DOM 元素并聚焦
          };
        
          return (
            <div>
              <input type="text" ref={inputRef} />
              <button onClick={handleFocus}>聚焦输入框</button>
            </div>
          );
        }
        ```
       **注意:** DOM 引用仅在**组件挂载后**可用,通常在 `useEffect` 或事件处理函数中访问。

**[⬆ 返回顶部](#table-of-contents)**

297. ### 能否使用 useRef 在渲染之间持久化值?
        是的,可以使用 `useRef` 在 React 中跨渲染持久化值。与 `useState` 不同,修改 `.current` 不会触发重新渲染,但值会在渲染之间保留。
        
        **示例:**
        ```js
        function Timer() {
          const renderCount = useRef(0);
          useEffect(() => {
            renderCount.current++;
            console.log("渲染次数:", renderCount.current);
          });
        
          return <div>查看控制台获取渲染次数。</div>;
        }
        ```
**[⬆ 返回顶部](#table-of-contents)**

298. ### 能否使用 useRef 存储先前的值?
        是的,当需要比较当前和先前的 props 或 state 而不触发重新渲染时,`useRef` 是一种常见模式。
        
        **示例:存储先前的状态值**
        ```js
        import { useEffect, useRef, useState } from 'react';
        
        function PreviousValueExample() {
          const [count, setCount] = useState(0);
          const prevCountRef = useRef();
        
          useEffect(() => {
            prevCountRef.current = count;
          }, [count]);
        
          const prevCount = prevCountRef.current;
        
          return (
            <div>
              <p>当前值: {count}</p>
              <p>先前值: {prevCount}</p>
              <button onClick={() => setCount(c => c + 1)}>增加</button>
            </div>
          );
        }
        ```
**[⬆ 返回顶部](#table-of-contents)**

299. ### 能否在渲染方法中访问 ref?
        可以在渲染方法中访问 ref,但获取到的内容取决于 ref 的使用方式以及在组件生命周期中的渲染时机。
        
        例如,当使用 ref 访问 DOM 节点(如 divRef.current)时,在首次渲染时它不会立即可用。
        ```js
        const divRef = useRef(null);
        
        console.log(divRef.current); // ❌ 初始渲染时为 null
        return <div ref={divRef}>Hello</div>;
        ```
**[⬆ 返回顶部](#table-of-contents)**

300. ### useRef 钩子的常见用例有哪些?
      一些常见用例包括:
      *   组件挂载时自动聚焦输入框。
      *   滚动到特定元素。
      *   测量元素尺寸(`offsetWidth`、`clientHeight`)。
      *   控制视频/音频播放。
      *   与非 React 库(如 D3 或 jQuery)集成。

**[⬆ 返回顶部](#table-of-contents)**

301. ### 什么是 useImperativeHandle 钩子?请举例说明。
      `useImperativeHandle` 是一个 React 钩子,当使用 `ref` 时,它允许**子组件**向其**父组件**暴露**自定义函数或属性**。
      它通常与 `forwardRef` 一起使用,在**模态框**、**对话框**、**自定义输入框**等场景中非常有用,这些场景中父组件需要**命令式地控制行为**(例如打开、关闭、重置)。

      **示例:对话框组件**
      ```js
      import React, {
        useRef,
        useState,
        useImperativeHandle,
        forwardRef,
      } from 'react';
      import './Dialog.css'; 

      const Dialog = forwardRef((props, ref) => {
        const [isOpen, setIsOpen] = useState(false);
        const [formData, setFormData] = useState('');

        useImperativeHandle(ref, () => ({
          open: () => setIsOpen(true),
          close: () => setIsOpen(false),
          reset: () => setFormData(''),
        }));

        if (!isOpen) return null;

        return (
          <div className="dialog"> 
            <h2>对话框</h2>
            <input
              type="text"
              value={formData}
              placeholder="输入内容..."
              onChange={(e) => setFormData(e.target.value)}
            />
            <br />
            <button onClick={() => setIsOpen(false)}>关闭</button>
          </div>
        );
      });

      function Parent() {
        const dialogRef = useRef();

        return (
          <div>
            <h1>useImperativeHandle 对话框示例</h1>
            <button onClick={() => dialogRef.current.open()}>打开对话框</button>
            <button onClick={() => dialogRef.current.reset()}>重置对话框</button>
            <button onClick={() => dialogRef.current.close()}>关闭对话框</button>

            <Dialog ref={dialogRef} />
          </div>
        );
      }

      export default Parent;
      ```
**[⬆ 返回顶部](#table-of-contents)**

302. ### 何时应该使用 useImperativeHandle?
      useImperativeHandle 钩子适用于以下情况:

      *   你希望从子组件暴露**命令式方法**
            - 自定义输入控件暴露 `focus`、`clear` 或 `validate` 方法
            - 模态框组件暴露 `open()` 和 `close()` 方法
            - 滚动容器暴露 `scrollToTop()` 或 `scrollToBottom()` 方法
      *   你希望**隐藏内部实现**但提供受控的外部访问。
      *   你正在构建**可复用组件库**(例如输入框、模态框、表单控件)。

**[⬆ 返回顶部](#table-of-contents)**

303. ### 是否可以在不使用 forwardRef 的情况下使用 useImperativeHandle?
        **否。** `useImperativeHandle` 仅在组件被 `forwardRef` 包装时才有效。这种组合允许父组件在函数组件上使用 `ref`。

**[⬆ 返回顶部](#table-of-contents)**

304. ### useMemo 与 useCallback 有何区别?
     下表比较了 useMemo 和 useCallback:

      | 特性 | `useMemo` | `useCallback` |
      | --- | --- | --- |
      | **用途** | 缓存**计算结果** | 缓存**函数引用** |
      | **返回值** | 一个**值**(例如函数的结果) | 一个**函数** |
      | **用法** | `useMemo(() => 计算值(), [依赖项])` | `useCallback(() => 执行操作(), [依赖项])` |
      | **主要用例** | 避免昂贵的重新计算 | 防止函数不必要的重新创建 |
      | **常见场景** | 过滤、排序、计算派生数据 | 向子组件传递回调函数 |
      | **有用时机** | 当值计算成本高时 | 当引用相等性重要时(例如 props) |
      | **重新计算时机** | 依赖项变化时 | 依赖项变化时 |
      | **返回值类型** | 任意类型(数字、对象、数组等) | 始终是函数 |
      | **开销** | 轻微(计算函数并缓存结果) | 轻微(缓存函数引用) |

**[⬆ 返回顶部](#table-of-contents)**

305. ### useMemo 是否会阻止子组件重新渲染?

        `useMemo` 钩子**不会直接阻止子组件重新渲染**。其主要目的是缓存昂贵计算的结果,以便除非依赖项变化,否则不会重新计算。虽然这可以提高性能,但它本身并不控制子组件是否重新渲染。
        
        然而,当缓存的值作为 prop 传递给被 `React.memo` 包装的子组件时,`useMemo` **可以帮助防止重新渲染**。在这种情况下,如果缓存的值在渲染之间没有变化(即具有相同的引用),React.memo 可以跳过子组件的重新渲染。因此,虽然 `useMemo` 本身不会停止渲染,但它**与 `React.memo` 等工具结合使用**可以优化渲染行为。

**[⬆ 返回顶部](#table-of-contents)**

306. ### 什么是 `useCallback`,为什么使用它?

        `useCallback` 是一个 React 钩子,用于在渲染之间**缓存函数定义**。它返回相同的函数引用,除非其依赖项发生变化。当将回调传递给经过优化的子组件(例如被 `React.memo` 包装的组件)时,这对于防止不必要的重新渲染特别有用。
        
        **示例:**
        
        ```css
        const handleClick = useCallback(() => {
          console.log('按钮被点击');
        }, []);
        ```
        
        如果没有 `useCallback`,每次渲染都会创建一个新函数,可能导致子组件不必要地重新渲染。

**[⬆ 返回顶部](#table-of-contents)**

307. ### 什么是自定义 React Hooks,如何开发一个?

      React 中的**自定义 Hooks** 是 JavaScript 函数,允许你使用 `useState`、`useEffect` 等 React 内置 Hooks**提取和复用组件逻辑**。

      它们以**"use"** 开头,让你能够封装多个组件可能共享的逻辑(例如数据获取、表单处理或计时器管理),而无需重复代码。

      让我们通过 `useFetchData` 示例来解释自定义 Hook 的用法。`useFetchData` 自定义 Hook 是 React 中的一个可复用函数,它简化了从 API 获取数据的过程。它封装了常见逻辑,如发起请求、管理加载和错误状态以及存储获取的数据。通过使用 `useState` 和 `useEffect` 等内置 Hooks,`useFetchData` 提供了一个简洁的接口,返回 `data`、`loading` 和 `error` 值,这些值可以直接在组件中使用。

      ```jsx
      import { useState, useEffect } from 'react';

      function useFetchData(url) {
        const [data, setData] = useState(null);     // 保存响应数据
        const [loading, setLoading] = useState(true); // 加载状态
        const [error, setError] = useState(null);     // 错误状态

        useEffect(() => {
          let isMounted = true; // 防止在已卸载组件上设置状态
          setLoading(true);

          fetch(url)
            .then((response) => {
              if (!response.ok) throw new Error('网络响应不正常');
              return response.json();
            })
            .then((json) => {
              if (isMounted) {
                setData(json);
                setLoading(false);
              }
            })
            .catch((err) => {
              if (isMounted) {
                setError(err.message);
                setLoading(false);
              }
            });

          return () => {
            isMounted = false; // 清理函数,避免内存泄漏
          };
        }, [url]);

        return { data, loading, error };
      }
      ```

      上述自定义 Hook 可用于 `AuthorList`、`ReviewerList` 等组件获取用户数据。

      **示例:AuthorList 组件**
      ```jsx
      function AuthorList() {
        const { data, loading, error } = useFetchData('https://api.example.com/authors');

        if (loading) return <p>加载作者中...</p>;
        if (error) return <p>错误: {error}</p>;

        return (
          <ul>
            {data.map((author) => (
              <li key={author.id}>{author.name}</li>
            ))}
          </ul>
        );
      }
      ```
     
      自定义 Hooks 的一些优点包括:
       *   促进**代码复用**
       *   保持组件**简洁专注**
       *   使复杂逻辑**更易于测试和维护**

**[⬆ 返回顶部](#table-of-contents)**

309. ### React Fiber 如何工作?请详细解释。

      React Fiber 是 React 的**核心引擎**,它支持**并发渲染**、**优先级排序**和**可中断性**等高级特性。其工作原理如下:
          
      ### 1. **Fiber 树结构**
          
      应用中的每个组件在树结构中由一个**Fiber 节点**表示。Fiber 节点包含:
      *   组件类型
      *   Props 和 state
      *   指向父节点、子节点和兄弟节点的指针
      *   用于跟踪变更的 Effect 标签(例如更新、放置)
      *   这形成了**Fiber 树**,一种 React 用来替代传统调用栈的数据结构。
          
      ### 2. **渲染的两个阶段**
          
        #### **A. 渲染阶段(工作进行中)**
          
      *   React 构建**工作进行中的 Fiber 树**。
      *   它遍历每个组件(开始阶段),计算需要变更的内容,并收集副作用(完成阶段)。
      *   此阶段是**可中断的**——React 可以暂停它,稍后再恢复。
        #### **B. 提交阶段**
          
      *   React 将变更应用到**真实 DOM**。
      *   运行生命周期方法(例如 `componentDidMount`、`useEffect`)。
      *   此阶段**不可中断**,但速度很快。
          
        ### 3. **工作单元和调度**
          
      *   React 将渲染分解为**工作单元**(小任务)。
      *   这些单元通过**React Scheduler** 基于**优先级**进行调度。
      *   如果时间用尽(例如用户开始输入),React 可以**暂停并将控制权交还给浏览器**。
          
        ### 4. **双缓冲与两棵树**
          
      *   React 维护两棵树:
      *   **当前树**——屏幕上可见的内容。
      *   **工作进行中的树**——内存中正在构建的下一版本。
      *   只有当新树完全准备好后,React 才会**提交**它,使其成为新的当前树。
          
        ### 5. **并发和优先级**
          
      *   React 可以同时准备 UI 的多个版本(例如在数据加载缓慢时)。
      *   更新可以**分配优先级**,因此紧急更新(如点击)比后台工作处理得更快。

**[⬆ 返回顶部](#table-of-contents)**

310. ### useId 钩子是什么,何时应该使用它?

     `useId` 钩子是 React 18 中引入的一个 React 钩子,用于生成在服务器和客户端渲染中**稳定的唯一 ID**。它主要用于**可访问性属性**,如将表单标签与输入框关联。

     #### 语法
     ```js
     const id = useId();
     ```

     #### 示例:可访问的表单输入
     ```jsx
     import { useId } from 'react';

     function EmailField() {
       const id = useId();
       
       return (
         <div>
           <label htmlFor={id}>邮箱:</label>
           <input id={id} type="email" />
         </div>
       );
     }
     ```

     #### 何时使用
     - 为表单元素生成唯一 ID(`htmlFor`、`aria-describedby`、`aria-labelledby`)
     - 在服务器端渲染(SSR)应用中创建稳定 ID
     - 当同一组件多次渲染时避免 ID 冲突

     #### 何时不使用
     - 作为列表中的 keys(改用基于数据的 keys)
     - 作为 CSS 选择器或查询选择器
     - 任何需要 ID 可预测的用途

     **注意:** `useId` 生成的 ID 包含冒号(`:`),这在 CSS 选择器中可能无法使用。对于多个相关 ID,可以使用相同的 `id` 作为前缀:`${id}-firstName`、`${id}-lastName`。

**[⬆ 返回顶部](#table-of-contents)**

311. ### useDeferredValue 钩子是什么?

     `useDeferredValue` 钩子用于**延迟更新 UI 的一部分**,以保持其他部分的响应性。它接受一个值并返回该值的"延迟"版本,该版本可能会滞后。这在优化渲染昂贵组件时非常有用。

     #### 语法
     ```js
     const deferredValue = useDeferredValue(value);
     ```

     #### 示例:带延迟结果的搜索
     ```jsx
     import { useState, useDeferredValue, useMemo } from 'react';

     function SearchResults({ query }) {
       // 昂贵的计算或大型列表过滤
       const results = useMemo(() => {
         return largeDataSet.filter(item => 
           item.name.toLowerCase().includes(query.toLowerCase())
         );
       }, [query]);

       return (
         <ul>
           {results.map(item => <li key={item.id}>{item.name}</li>)}
         </ul>
       );
     }

     function SearchPage() {
       const [query, setQuery] = useState('');
       const deferredQuery = useDeferredValue(query);
       const isStale = query !== deferredQuery;

       return (
         <div>
           <input 
             value={query} 
             onChange={(e) => setQuery(e.target.value)}
             placeholder="搜索..."
           />
           <div style={{ opacity: isStale ? 0.5 : 1 }}>
             <SearchResults query={deferredQuery} />
           </div>
         </div>
       );
     }
     ```

     输入框保持响应,而昂贵的 `SearchResults` 组件使用延迟值进行轻微延迟的重新渲染。

**[⬆ 返回顶部](#table-of-contents)**

312. ### useTransition 钩子是什么,它与 useDeferredValue 有何区别?

     `useTransition` 钩子允许你将某些状态更新标记为**非紧急过渡**,在昂贵的重新渲染期间保持 UI 响应。它返回一个 `isPending` 标志和一个 `startTransition` 函数。

     #### 语法
     ```js
     const [isPending, startTransition] = useTransition();
     ```

     #### 示例:标签切换
     ```jsx
     import { useState, useTransition } from 'react';

     function TabContainer() {
       const [isPending, startTransition] = useTransition();
       const [tab, setTab] = useState('home');

       function selectTab(nextTab) {
         startTransition(() => {
           setTab(nextTab);
         });
       }

       return (
         <div>
           <button onClick={() => selectTab('home')}>首页</button>
           <button onClick={() => selectTab('posts')}>文章(较慢)</button>
           <button onClick={() => selectTab('contact')}>联系我们</button>
           
           {isPending && <Spinner />}
           
           {tab === 'home' && <HomeTab />}
           {tab === 'posts' && <PostsTab />}  {/* 昂贵的组件 */}
           {tab === 'contact' && <ContactTab />}
         </div>
       );
     }
     ```

     #### 与 useDeferredValue 的区别

     | 特性 | useTransition | useDeferredValue |
     |---------|--------------|------------------|
     | 控制对象 | 状态更新(包装 `setState`) | 值(包装一个值) |
     | 用例 | 当你控制状态更新时 | 当你从 props 或其他 hooks 接收值时 |
     | 返回值 | `[isPending, startTransition]` | 延迟值 |
     | 挂起状态 | 内置 `isPending` 标志 | 需要手动比较 |

**[⬆ 返回顶部](#table-of-contents)**

313. ### useSyncExternalStore 钩子有什么作用?

     `useSyncExternalStore` 钩子旨在以**订阅外部存储**(非 React 状态源)的方式,确保与并发渲染兼容。它主要供库作者用于状态管理库。

     #### 语法
     ```js
     const state = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?);
     ```

     - **subscribe**:用于订阅存储的函数,返回一个取消订阅函数
     - **getSnapshot**:返回当前存储值的函数
     - **getServerSnapshot**:可选的 SSR 函数,返回初始服务器快照

     #### 示例:浏览器在线状态
     ```jsx
     import { useSyncExternalStore } from 'react';

     function getSnapshot() {
       return navigator.onLine;
     }

     function subscribe(callback) {
       window.addEventListener('online', callback);
       window.addEventListener('offline', callback);
       return () => {
         window.removeEventListener('online', callback);
         window.removeEventListener('offline', callback);
       };
     }

     function useOnlineStatus() {
       return useSyncExternalStore(subscribe, getSnapshot, () => true);
     }

     function StatusBar() {
       const isOnline = useOnlineStatus();
       return <h1>{isOnline ? '✅ 在线' : '❌ 已断开连接'}</h1>;
     }
     ```

     此钩子确保当外部存储发生变化时,React 能够一致地重新渲染,不会出现撕裂(显示不一致数据)的情况。

**[⬆ 返回顶部](#table-of-contents)**

314. ### useInsertionEffect 钩子是什么?

     `useInsertionEffect` 钩子专为**CSS-in-JS 库作者**设计,用于在任何布局效果运行之前将样式注入 DOM。它在 DOM 突变前同步触发。

     #### 语法
     ```js
     useInsertionEffect(() => {
       // 在此处插入样式
       return () => {
         // 清理
       };
     }, [dependencies]);
     ```

     #### 执行顺序
     ```
     1. useInsertionEffect  → 注入样式
     2. DOM mutations       → React 更新 DOM
     3. useLayoutEffect     → 读取布局,必要时同步重新渲染
     4. Browser paint       → 用户看到结果
     5. useEffect           → 运行副作用
     ```

     #### 示例:动态样式注入
     ```jsx
     import { useInsertionEffect } from 'react';

     let isInserted = new Set();

     function useCSS(rule) {
       useInsertionEffect(() => {
         if (!isInserted.has(rule)) {
           isInserted.add(rule);
           const style = document.createElement('style');
           style.textContent = rule;
           document.head.appendChild(style);
         }
       }, [rule]);
     }

     function Button() {
       useCSS('.dynamic-btn { background: blue; color: white; }');
       return <button className="dynamic-btn">点击我</button>;
     }
     ```

     **注意**:此钩子并非用于应用代码。它专门供 styled-components 或 Emotion 等 CSS-in-JS 库使用,以防止样式闪烁。

**[⬆ 返回顶部](#table-of-contents)**

315. ### 如何使用自定义钩子在组件之间共享状态逻辑?

     自定义钩子允许你在不改变组件层次结构的情况下**提取和共享有状态逻辑**。状态本身不会被共享——每个使用钩子的组件都获得自己独立的状态。

     #### 示例:useLocalStorage 钩子
     ```jsx
     import { useState, useEffect } from 'react';

     function useLocalStorage(key, initialValue) {
       // 获取存储的值或使用初始值
       const [storedValue, setStoredValue] = useState(() => {
         try {
           const item = window.localStorage.getItem(key);
           return item ? JSON.parse(item) : initialValue;
         } catch (error) {
           console.error(error);
           return initialValue;
         }
       });

       // 当状态变化时更新 localStorage
       useEffect(() => {
         try {
           window.localStorage.setItem(key, JSON.stringify(storedValue));
         } catch (error) {
           console.error(error);
         }
       }, [key, storedValue]);

       return [storedValue, setStoredValue];
     }

     // 在多个组件中使用
     function ThemeToggle() {
       const [theme, setTheme] = useLocalStorage('theme', 'light');
       return (
         <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
           当前:{theme}
         </button>
       );
     }

     function FontSizeSelector() {
       const [fontSize, setFontSize] = useLocalStorage('fontSize', 16);
       return (
         <input 
           type="range" 
           value={fontSize} 
           onChange={(e) => setFontSize(Number(e.target.value))} 
         />
       );
     }
     ```

     两个组件都使用 `useLocalStorage`,但每个组件都有**自己独立的状态**,并持久化到 localStorage 中。

**[⬆ 返回顶部](#table-of-contents)**

316. ### useDebugValue 钩子是什么?

     `useDebugValue` 钩子用于在**React DevTools**中为自定义钩子**显示标签**。它通过展示有意义的信息,帮助开发人员调试自定义钩子。

     #### 语法
     ```js
     useDebugValue(value);
     useDebugValue(value, formatFn); // 带可选格式化函数
     ```

     #### 示例:带调试值的自定义钩子
     ```jsx
     import { useState, useEffect, useDebugValue } from 'react';

     function useOnlineStatus() {
       const [isOnline, setIsOnline] = useState(true);

       useEffect(() => {
         const handleOnline = () => setIsOnline(true);
         const handleOffline = () => setIsOnline(false);
         
         window.addEventListener('online', handleOnline);
         window.addEventListener('offline', handleOffline);
         
         return () => {
           window.removeEventListener('online', handleOnline);
           window.removeEventListener('offline', handleOffline);
         };
       }, []);

       // 在 DevTools 中显示 "OnlineStatus: Online" 或 "OnlineStatus: Offline"
       useDebugValue(isOnline ? '在线' : '离线');

       return isOnline;
     }
     ```

     #### 使用格式化函数(用于耗时计算)
     ```jsx
     function useUser(userId) {
       const [user, setUser] = useState(null);
       
       // 格式化函数仅在 DevTools 打开时运行
       useDebugValue(user, (user) => user ? `用户:${user.name}` : '加载中...');
       
       return user;
     }
     ```

     **注意**:仅在作为共享库一部分的自定义钩子中使用 `useDebugValue`。应用代码中的每个自定义钩子都不需要它。

**[⬆ 返回顶部](#table-of-contents)**

317. ### 如何在 useEffect 中处理清理?

     `useEffect` 中的清理函数用于在组件卸载前或效果再次运行前**清理副作用**。这可以防止内存泄漏、数据过期和意外行为。

     #### 语法
     ```js
     useEffect(() => {
       // 设置代码
       
       return () => {
         // 清理代码
       };
     }, [dependencies]);
     ```

     #### 常见清理场景

     **1. 事件监听器**
     ```jsx
     useEffect(() => {
       const handleResize = () => setWidth(window.innerWidth);
       window.addEventListener('resize', handleResize);
       
       return () => window.removeEventListener('resize', handleResize);
     }, []);
     ```

     **2. 定时器和间隔器**
     ```jsx
     useEffect(() => {
       const intervalId = setInterval(() => {
         setCount(c => c + 1);
       }, 1000);
       
       return () => clearInterval(intervalId);
     }, []);
     ```

     **3. 订阅**
     ```jsx
     useEffect(() => {
       const subscription = dataSource.subscribe(handleChange);
       
       return () => subscription.unsubscribe();
     }, [dataSource]);
     ```

     **4. 中止 Fetch 请求**
     ```jsx
     useEffect(() => {
       const controller = new AbortController();
       
       fetch(url, { signal: controller.signal })
         .then(response => response.json())
         .then(data => setData(data))
         .catch(err => {
           if (err.name !== 'AbortError') {
             setError(err);
           }
         });
       
       return () => controller.abort();
     }, [url]);
     ```

     **清理运行时机**:
     - 组件卸载前
     - 依赖项变化导致效果重新运行前

**[⬆ 返回顶部](#table-of-contents)**

317. ### useEffect 和 useEvent(实验性)有什么区别?

     `useEvent` 是一个**实验性钩子**(尚未在 React 中稳定),旨在解决创建**稳定事件处理程序**的问题,这些处理程序始终访问最新的 props 和状态,而不会导致重新渲染或需要放入依赖数组中。

     #### useEvent 解决的问题
     ```jsx
     // 问题:onTick 在每次渲染时变化,导致间隔器重置
     function Timer({ onTick }) {
       useEffect(() => {
         const id = setInterval(() => {
           onTick(); // 如果 onTick 不在依赖中,则使用陈旧的闭包
         }, 1000);
         return () => clearInterval(id);
       }, [onTick]); // 添加 onTick 导致间隔器频繁重置
     }
     ```

     #### 使用 useEvent 的解决方案(实验性)
     ```jsx
     import { useEvent } from 'react'; // 实验性

     function Timer({ onTick }) {
       const stableOnTick = useEvent(onTick);

       useEffect(() => {
         const id = setInterval(() => {
           stableOnTick(); // 始终调用最新的 onTick
         }, 1000);
         return () => clearInterval(id);
       }, []); // 不需要依赖!
     }
     ```

     #### 主要区别

     | 特性 | useEffect | useEvent(实验性) |
     |---------|-----------|------------------------|
     | 用途 | 运行副作用 | 创建稳定的回调函数 |
     | 运行时机 | 渲染后 | 渲染期间(创建函数) |
     | 返回值 | 清理函数 | 稳定的事件处理程序 |
     | 闭包 | 捕获渲染时的值 | 始终访问最新值 |
     | 依赖项 | 必须列出所有使用的值 | 不需要在其他钩子的依赖中 |

     **注意**:在 `useEvent` 稳定之前,你可以使用 `useCallback` 和 `useRef` 作为稳定回调的替代方案。

**[⬆ 返回顶部](#table-of-contents)**

319. ### 使用 React Hooks 的最佳实践有哪些?

     遵循最佳实践可确保你的钩子具有可预测性、可维护性且无 bug。

     #### 1. **遵循 Hooks 规则**
     - 只在顶层调用钩子(不在循环、条件或嵌套函数中)
     - 只从 React 函数中调用钩子(组件或自定义钩子)

     #### 2. **使用 ESLint 插件**
     ```bash
     npm install eslint-plugin-react-hooks --save-dev
     ```
     ```json
     {
       "plugins": ["react-hooks"],
       "rules": {
         "react-hooks/rules-of-hooks": "error",
         "react-hooks/exhaustive-deps": "warn"
       }
     }
     ```

     #### 3. **保持钩子专注且简单**
     ```jsx
     // ❌ 不好:一个钩子做太多事情
     function useEverything() {
       const [user, setUser] = useState(null);
       const [posts, setPosts] = useState([]);
       const [theme, setTheme] = useState('light');
       // ... 大量不相关的逻辑
     }

     // ✅ 好:分离关注点
     function useUser() { /* 用户逻辑 */ }
     function usePosts() { /* 帖子逻辑 */ }
     function useTheme() { /* 主题逻辑 */ }
     ```

     #### 4. **为自定义钩子使用描述性名称**
     ```jsx
     // ❌ 不好
     function useData() { }

     // ✅ 好
     function useUserAuthentication() { }
     function useFetchProducts() { }
     function useFormValidation() { }
     ```

     #### 5. **正确管理依赖项**
     ```jsx
     // ❌ 不好:缺少依赖项
     useEffect(() => {
       fetchUser(userId);
     }, []); // 缺少 userId

     // ✅ 好:列出所有依赖项
     useEffect(() => {
       fetchUser(userId);
     }, [userId]);
     ```

     #### 6. **避免内联对象/函数依赖项**
     ```jsx
     // ❌ 不好:每次渲染创建新对象
     useEffect(() => {
       doSomething(options);
     }, [{ page: 1, limit: 10 }]); // 引用始终不同

     // ✅ 好:记忆化或提取
     const options = useMemo(() => ({ page: 1, limit: 10 }), []);
     useEffect(() => {
       doSomething(options);
     }, [options]);
     ```

     #### 7. **清理副作用**
     订阅事件、定时器或外部数据源时,始终返回清理函数。

**[⬆ 返回顶部](#table-of-contents)**

## 现代 React 特性(React 18/19)
320. ### React 18 引入了哪些关键特性?

     React 18 于 2022 年 3 月发布,引入了多项突破性特性,重点关注性能和用户体验:

     #### 1. **自动批处理(Automatic Batching)**
     将多个状态更新合并处理(即使在异步代码中),以减少重渲染次数。
     ```jsx
     // React 18 之前:仅在事件处理程序中进行批处理
     // React 18 之后:在任何地方都能批处理
     setTimeout(() => {
       setCount(c => c + 1);
       setFlag(f => !f);
       // React 18 中仅触发 1 次重渲染!
     }, 1000);
     ```

     #### 2. **并发特性(Concurrent Features)**
     - **useTransition**:标记非紧急更新
     - **useDeferredValue**:延迟处理耗时的重渲染
     - **服务端 Suspense**:支持 Suspense 的服务端渲染(SSR)

     #### 3. **新的 createRoot API**
     ```jsx
     // 旧方式(React 17)
     ReactDOM.render(<App />, document.getElementById('root'));

     // 新方式(React 18)
     import { createRoot } from 'react-dom/client';
     const root = createRoot(document.getElementById('root'));
     root.render(<App />);
     ```

     #### 4. **结合 Suspense 的流式 SSR(Streaming SSR with Suspense)**
     从服务器流式传输 HTML,并在组件到达时进行 hydration(水合)。

     #### 5. **新的 Hooks**
     - `useId`:生成用于可访问性的唯一 ID
     - `useSyncExternalStore`:订阅外部存储
     - `useInsertionEffect`:供 CSS-in-JS 库使用

     #### 6. **严格模式改进(Strict Mode Improvements)**
     在开发环境中双重调用 effects,以捕获潜在问题。

**[⬆ 返回顶部](#table-of-contents)**

321. ### React 19 引入了哪些关键特性?

     React 19(2024 年发布)为全栈 React 应用带来了重大改进:

     #### 1. **React 编译器(原 React Forget)**
     自动记忆化 — 不再需要手动使用 `useMemo`、`useCallback` 或 `React.memo`!
     ```jsx
     // 之前:手动优化
     const memoizedValue = useMemo(() => expensiveCalc(a, b), [a, b]);
     
     // React 19:编译器自动优化
     const value = expensiveCalc(a, b); // 自动优化!
     ```

     #### 2. **服务器操作(Server Actions)**
     直接从组件调用服务器函数:
     ```jsx
     async function createPost(formData) {
       'use server'
       const post = await db.posts.create({
         title: formData.get('title')
       });
       revalidatePath('/posts');
       return post;
     }

     function NewPost() {
       return (
         <form action={createPost}>
           <input name="title" />
           <button type="submit">创建</button>
         </form>
       );
     }
     ```

     #### 3. **操作与表单操作(Actions & Form Actions)**
     自动处理 pending 状态、错误和乐观更新:
     ```jsx
     function Form() {
       const [state, formAction] = useFormState(serverAction, initialState);
       const { pending } = useFormStatus();
       
       return (
         <form action={formAction}>
           <input disabled={pending} />
           <button disabled={pending}>
             {pending ? '提交中...' : '提交'}
           </button>
         </form>
       );
     }
     ```

     #### 4. **use() Hook**
     在渲染期间读取资源(Promises、Context):
     ```jsx
     function User({ userPromise }) {
       const user = use(userPromise); // 挂起直到解析完成
       return <div>{user.name}</div>;
     }
     ```

     #### 5. **useOptimistic Hook**
     实现乐观 UI 更新:
     ```jsx
     function TodoList({ todos }) {
       const [optimisticTodos, addOptimisticTodo] = useOptimistic(
         todos,
         (state, newTodo) => [...state, { ...newTodo, pending: true }]
       );

       async function createTodo(title) {
         addOptimisticTodo({ id: Date.now(), title });
         await saveTodo(title);
       }

       return optimisticTodos.map(todo => (
         <Todo key={todo.id} {...todo} />
       ));
     }
     ```

     #### 6. **文档元数据(Document Metadata)**
     内置支持 `<title>`、`<meta>` 等标签:
     ```jsx
     function BlogPost({ post }) {
       return (
         <>
           <title>{post.title}</title>
           <meta name="description" content={post.excerpt} />
           <article>{post.content}</article>
         </>
       );
     }
     ```

     #### 7. **资源加载 API(Asset Loading APIs)**
     预加载资源以提升性能:
     ```jsx
     import { preload, preinit } from 'react-dom';

     preload('/font.woff2', { as: 'font' });
     preinit('/script.js', { as: 'script' });
     ```
**[⬆ 返回顶部](#table-of-contents)**

322. ### React 19 中的 use() hook 是什么?

     `use()` hook 允许你在渲染期间读取资源(Promise 或 Context)的值,并与 Suspense 集成。

     #### 读取 Promises
     ```jsx
     import { use, Suspense } from 'react';

     function UserProfile({ userPromise }) {
       const user = use(userPromise); // 挂起直到解析完成
       
       return (
         <div>
           <h1>{user.name}</h1>
           <p>{user.email}</p>
         </div>
       );
     }

     function App() {
       const userPromise = fetchUser(123);
       
       return (
         <Suspense fallback={<div>加载中...</div>}>
           <UserProfile userPromise={userPromise} />
         </Suspense>
       );
     }
     ```

     #### 读取 Context
     ```jsx
     import { use } from 'react';
     import { ThemeContext } from './context';

     function Button() {
       const theme = use(ThemeContext);
       return <button className={theme}>点击我</button>;
     }
     ```

     #### 与其他 Hooks 的主要区别

     | 特性 | use() | useContext() | useState() |
     |---------|-------|--------------|------------|
     | 可条件性调用 | ✅ 是 | ❌ 否 | ❌ 否 |
     | 可在循环中调用 | ✅ 是 | ❌ 否 | ❌ 否 |
     | 对 Promises 挂起 | ✅ 是 | ❌ 不适用 | ❌ 不适用 |
     | 读取 Context | ✅ 是 | ✅ 是 | ❌ 不适用 |

     #### 条件性使用(独特之处!)
     ```jsx
     function Component({ showUser, userPromise }) {
       // ✅ use() 允许这样使用!
       const user = showUser ? use(userPromise) : null;
       
       return user ? <div>{user.name}</div> : <div>无用户</div>;
     }
     ```
**[⬆ 返回顶部](#table-of-contents)**

323. ### React 19 中的服务器操作(Server Actions)是什么?

     **服务器操作(Server Actions)** 允许你直接从客户端组件调用服务器端函数,无需编写 API 端点。

     #### 基本服务器操作
     ```jsx
     // app/actions.js
     'use server'

     export async function createPost(formData) {
       const title = formData.get('title');
       const content = formData.get('content');
       
       const post = await db.posts.create({
         title,
         content,
         userId: await getCurrentUser()
       });
       
       revalidatePath('/posts');
       redirect(`/posts/${post.id}`);
     }
     ```

     #### 在表单中使用
     ```jsx
     // app/new-post.jsx
     import { createPost } from './actions';

     export default function NewPost() {
       return (
         <form action={createPost}>
           <input name="title" required />
           <textarea name="content" required />
           <button type="submit">创建帖子</button>
         </form>
       );
     }
     ```

     #### 使用 useFormState 获取加载状态
     ```jsx
     'use client'
     import { useFormState } from 'react-dom';
     import { createPost } from './actions';

     export default function NewPost() {
       const [state, formAction] = useFormState(createPost, { message: '' });
       
       return (
         <form action={formAction}>
           <input name="title" required />
           <textarea name="content" required />
           <button type="submit">创建帖子</button>
           {state.message && <p>{state.message}</p>}
         </form>
       );
     }
     ```

     #### 渐进式增强
     即使 JavaScript 被禁用,服务器操作仍然有效!
     ```jsx
     // 此表单在没有 JavaScript 的情况下也能工作
     <form action={serverAction}>
       <input name="email" type="email" />
       <button>订阅</button>
     </form>
     ```

     #### 安全性
     - 自动受 CSRF 保护
     - 始终在服务器上运行(从不暴露给客户端)
     - 可安全使用仅服务器端的包

**[⬆ 返回顶部](#table-of-contents)**

324. ### useFormState 和 useFormStatus hooks 是什么?

     这些 hooks 简化了 React 19 中结合服务器操作的表单处理。

     #### useFormState
     管理表单状态并处理服务器响应:
     ```jsx
     'use client'
     import { useFormState } from 'react-dom';
     import { loginAction } from './actions';

     export default function LoginForm() {
       const [state, formAction] = useFormState(loginAction, {
         errors: {},
         message: ''
       });

       return (
         <form action={formAction}>
           <input name="email" type="email" />
           {state.errors.email && <p>{state.errors.email}</p>}
           
           <input name="password" type="password" />
           {state.errors.password && <p>{state.errors.password}</p>}
           
           <button type="submit">登录</button>
           {state.message && <p>{state.message}</p>}
         </form>
       );
     }
     ```

     #### useFormStatus
     获取父表单的 pending 状态:
     ```jsx
     'use client'
     import { useFormStatus } from 'react-dom';

     function SubmitButton() {
       const { pending, data, method, action } = useFormStatus();
       
       return (
         <button type="submit" disabled={pending}>
           {pending ? '提交中...' : '提交'}
         </button>
       );
     }

     // 必须在 <form> 的子组件中使用
     export default function MyForm() {
       return (
         <form action={serverAction}>
           <input name="email" />
           <SubmitButton />
         </form>
       );
     }
     ```

     #### 两者结合使用
     ```jsx
     'use client'
     import { useFormState, useFormStatus } from 'react-dom';

     function SubmitButton() {
       const { pending } = useFormStatus();
       return (
         <button disabled={pending}>
           {pending ? '⏳ 保存中...' : '💾 保存'}
         </button>
       );
     }

     export default function EditProfile() {
       const [state, formAction] = useFormState(updateProfile, null);

       return (
         <form action={formAction}>
           <input name="name" defaultValue={user.name} />
           <input name="bio" defaultValue={user.bio} />
           <SubmitButton />
           {state?.success && <p>✅ 个人资料已更新!</p>}
           {state?.error && <p>❌ {state.error}</p>}
         </form>
       );
     }
     ```

     #### 关键点
     - `useFormState`:用于管理服务器响应和错误
     - `useFormStatus`:用于提交过程中的 UI 反馈
     - `useFormStatus` 必须在表单的子组件中使用
     - 与服务器操作无缝协作

**[⬆ 返回顶部](#table-of-contents)**

325. ### useOptimistic hook 是什么?

     `useOptimistic` 支持乐观 UI 更新 — 在服务器确认之前立即显示更改。

     #### 基本用法
     ```jsx
     import { useOptimistic } from 'react';

     function TodoList({ todos, addTodo }) {
       const [optimisticTodos, addOptimisticTodo] = useOptimistic(
         todos,
         (currentTodos, newTodo) => [...currentTodos, { ...newTodo, pending: true }]
       );

       async function handleSubmit(formData) {
         const title = formData.get('title');
         
         // 立即显示乐观更新
         addOptimisticTodo({ id: Date.now(), title });
         
         // 发送到服务器
         await addTodo(title);
         // 完成后组件将使用实际数据重新渲染
       }

       return (
         <>
           <form action={handleSubmit}>
             <input name="title" />
             <button>添加</button>
           </form>
           
           <ul>
             {optimisticTodos.map(todo => (
               <li key={todo.id} style={{ opacity: todo.pending ? 0.5 : 1 }}>
                 {todo.title}
                 {todo.pending && ' ⏳'}
               </li>
             ))}
           </ul>
         </>
       );
     }
     ```

     #### 结合服务器操作使用
     ```jsx
     'use client'
     import { useOptimistic } from 'react';
     import { likePost } from './actions';

     export default function Post({ post, likes }) {
       const [optimisticLikes, addOptimisticLike] = useOptimistic(
         likes,
         (currentLikes, amount) => currentLikes + amount
       );

       async function handleLike() {
         addOptimisticLike(1); // 立即更新 UI
         await likePost(post.id); // 服务器更新
       }

       return (
         <div>
           <h2>{post.title}</h2>
           <button onClick={handleLike}>
             ❤️ {optimisticLikes} 个赞
           </button>
         </div>
       );
     }
     ```

     #### 带错误处理的复杂示例
     ```jsx
     function ShoppingCart({ items, removeItem }) {
       const [optimisticItems, removeOptimistic] = useOptimistic(
         items,
         (current, removedId) => current.filter(item => item.id !== removedId)
       );

       async function handleRemove(itemId) {
         removeOptimistic(itemId); // 立即从 UI 中移除
         
         try {
           await removeItem(itemId);
         } catch (error) {
           // 出错时自动回滚!
           toast.error('移除商品失败');
         }
       }

       return (
         <ul>
           {optimisticItems.map(item => (
             <li key={item.id}>
               {item.name}
               <button onClick={() => handleRemove(item.id)}>移除</button>
             </li>
           ))}
         </ul>
       );
     }
     ```

     #### 使用场景
     - ✅ 点赞/收藏切换
     - ✅ 列表项添加/移除
     - ✅ 聊天消息发送
     - ✅ 任何需要即时反馈提升用户体验的操作
     - ❌ 金融交易(需等待确认)
     - ❌ 需要服务器验证的关键操作

**[⬆ 返回顶部](#table-of-contents)**

326. ### React 编译器(React Forget)是什么?

     **React 编译器**(前身为 React Forget)通过在需要的地方自动添加记忆化来优化组件 — 无需手动使用 `useMemo`、`useCallback` 和 `React.memo`。

     #### React 编译器之前
     ```jsx
     function TodoList({ todos, filter }) {
       // 需要手动优化
       const filteredTodos = useMemo(() => {
         return todos.filter(todo => todo.status === filter);
       }, [todos, filter]);

       const handleToggle = useCallback((id) => {
         toggleTodo(id);
       }, [toggleTodo]);

       return (
         <div>
           {filteredTodos.map(todo => (
             <TodoItem 
               key={todo.id} 
               todo={todo} 
               onToggle={handleToggle} 
             />
           ))}
         </div>
       );
     }

     // 需要用 React.memo 包装
     export default React.memo(TodoList);
     ```

     #### 使用 React 编译器后
     ```jsx
     function TodoList({ todos, filter }) {
       // 编译器自动优化!
       const filteredTodos = todos.filter(todo => todo.status === filter);

       const handleToggle = (id) => {
         toggleTodo(id);
       };

       return (
         <div>
           {filteredTodos.map(todo => (
             <TodoItem 
               key={todo.id} 
               todo={todo} 
               onToggle={handleToggle} 
             />
           ))}
         </div>
       );
     }

     // 无需 React.memo!
     export default TodoList;
     ```

     #### 工作原理
     1. **构建时分析代码**
     2. **识别耗时计算**和渲染
     3. **自动插入记忆化**到有益的地方
     4. **保留 React 语义** — 代码行为保持正确

     #### 优势
     - ✅ 代码更简洁 — 无需手动优化
     - ✅ 默认性能更优
     - ✅ 减少因依赖项错误导致的 bug
     - ✅ 更易于维护和阅读
     - ✅ 与现有代码兼容

     #### 启用 React 编译器
     ```jsx
     // next.config.js(Next.js)
     module.exports = {
       experimental: {
         reactCompiler: true
       }
     }

     // vite.config.js(Vite)
     import { defineConfig } from 'vite'
     import react from '@vitejs/plugin-react'

     export default defineConfig({
       plugins: [
         react({
           babel: {
             plugins: [['babel-plugin-react-compiler']]
           }
         })
       ]
     })
     ```

     #### 仍需手动优化的场景
     ```jsx
     // 对于不支持编译器的外部库
     import { expensiveLibFunction } from 'old-library';

     function MyComponent() {
       // 这里可能仍需要手动记忆化
       const result = useMemo(() => expensiveLibFunction(), []);
       return <div>{result}</div>;
     }
     ```

     #### 兼容性
     - 适用于 React 18.3+ 和 React 19
     - 与 TypeScript 兼容
     - 支持所有 React hooks
     - 支持服务器组件和客户端组件

**[⬆ 返回顶部](#table-of-contents)**

327. ### 什么是流式 SSR?React 18+ 对其有何改进?

     **流式 SSR** 会在 HTML 生成时将其分块发送到浏览器,而不是等待整个页面生成完成。React 18+ 通过集成 Suspense,极大地改进了这一功能。

     #### 传统 SSR(React 18 之前)
     ```
     服务器:等待所有数据 → 生成所有 HTML → 发送到客户端
     客户端:接收 HTML → 下载所有 JS →  hydration 所有组件
     问题:缓慢的组件会阻塞整个页面!
     ```

     #### 流式 SSR(React 18+)
     ```
     服务器:随时发送已准备好的 HTML,将缓慢部分用 <Suspense> 包裹
     客户端:立即渲染,逐步进行 hydration
     优势:用户更快看到内容!
     ```

     #### 基本示例
     ```jsx
     import { Suspense } from 'react';

     export default function Page() {
       return (
         <html>
           <body>
             {/* 立即发送 */}
             <header>
               <h1>My App</h1>
             </header>

             {/* 立即发送并附带 fallback */}
             <Suspense fallback={<div>Loading comments...</div>}>
               <Comments /> {/* 准备就绪后流式传输 */}
             </Suspense>

             {/* 也单独流式传输 */}
             <Suspense fallback={<div>Loading recommendations...</div>}>
               <Recommendations /> {/* 准备就绪后流式传输 */}
             </Suspense>

             <footer>© 2026</footer>
           </body>
         </html>
       );
     }
     ```

     #### 带数据获取的服务器组件
     ```jsx
     // 这是一个服务器组件(异步的!)
     async function Comments() {
       const comments = await db.comments.findMany();
       
       return (
         <ul>
           {comments.map(comment => (
             <li key={comment.id}>{comment.text}</li>
           ))}
         </ul>
       );
     }
     ```

     #### 工作原理
     1. 服务器立即开始发送 HTML
     2. 当遇到 `<Suspense>` 时,发送 fallback 内容
     3. 继续流式传输页面的其余部分
     4. 当数据准备就绪后,发送实际组件
     5. 客户端用真实内容替换 fallback
     6. 每个组件独立进行 hydration

     #### 选择性 Hydration
     ```jsx
     function App() {
       return (
         <div>
           <header>Header</header> {/* 首先进行 hydration */}
           
           <Suspense fallback={<Spinner />}>
             <HeavyComponent /> {/* 用户交互时进行 hydration */}
           </Suspense>
           
           <Suspense fallback={<Spinner />}>
             <Comments /> {/* 独立进行 hydration */}
           </Suspense>
         </div>
       );
     }
     ```

     #### 优势
     - **更快的 TTFB(首字节时间)**:用户更快看到内容
     - **更好的用户体验**:渐进式加载,告别空白屏幕
     - **优先 hydration**:交互元素优先进行 hydration
     - **弹性更强**:缓慢组件不会阻塞快速组件

     #### Next.js App Router 示例
     ```jsx
     // app/page.tsx
     import { Suspense } from 'react';
     import ProductList from './ProductList';
     import Reviews from './Reviews';

     export default function ProductPage() {
       return (
         <div>
           <h1>Product Page</h1>
           
           {/* 首先流式传输产品列表 */}
           <Suspense fallback={<ProductSkeleton />}>
             <ProductList />
           </Suspense>

           {/* 评论单独流式传输 */}
           <Suspense fallback={<ReviewSkeleton />}>
             <Reviews />
           </Suspense>
         </div>
       );
     }

     // 这些是异步服务器组件
     async function ProductList() {
       const products = await fetchProducts(); // 不会阻塞 Reviews
       return <div>{/* 渲染产品 */}</div>;
     }

     async function Reviews() {
       const reviews = await fetchReviews(); // 不会阻塞 ProductList
       return <div>{/* 渲染评论 */}</div>;
     }
     ```

     #### 关键要求
     - 使用 React 18+ 及 `createRoot` 和 `hydrateRoot`
     - 用 `<Suspense>` 包裹缓慢组件
     - 使用支持流式传输的框架(Next.js、Remix 等)
     - 服务器必须支持流式响应

**[⬆ Back to Top](#table-of-contents)**

## 旧问答
1. ### 为什么不应直接更新 state?

   如果尝试直接更新 state,组件将不会重新渲染。

   ```javascript
   //错误示例
   this.state.message = "Hello world";

应改用 setState() 方法。它会安排更新组件的 state 对象。当 state 发生变化时,组件会通过重新渲染做出响应。

//正确示例
this.setState({ message: "Hello World" });

注意: 可以在 constructor 中直接为 state 对象赋值,或者使用最新的 JavaScript 类字段声明语法。

⬆ 返回顶部

  1. setState() 的参数中回调函数的作用是什么?

    作为 setState 第二个参数提供的回调函数会在 state 更新完成且组件重新渲染后执行。由于 setState() 是异步的,在调用 setState 后立即执行需要依赖更新后 state 的操作并不可靠。该回调确保代码仅在更新和重新渲染完成后运行。

    示例

    this.setState({ name: "Sudheer" }, () => {
      console.log("名称已更新,组件已重新渲染。");
    });
    

    何时使用回调?

    当需要在 DOM 响应 state 变化更新后立即执行操作时,使用 setState 回调。即,由于 React 中 state 更新的异步特性,当时间至关重要时,回调是在 state 更新和重新渲染后执行操作的可靠方式。例如,如果需要与更新后的 DOM 交互、触发分析或执行依赖于新 state 或渲染输出的进一步计算。

    注意

    • 在现代 React(函数组件)中,可以使用 useEffect Hook 来响应 state 变化,实现类似效果。
    • 在类组件中,也可以使用 componentDidUpdate 等生命周期方法进行更广泛的更新后逻辑处理。
    • setState 回调对于直接跟随特定 state 变化的一次性操作仍然很有用。

    ⬆ 返回顶部

  2. 如何在 JSX 回调中绑定方法或事件处理程序?

    在类组件中,有 3 种可能的实现方式:

    1. 在构造函数中绑定: 在 JavaScript 类中,方法默认是未绑定的。React 事件处理程序作为类方法定义时也遵循相同规则。通常我们在构造函数中绑定它们。

      class User extends Component {
        constructor(props) {
          super(props);
          this.handleClick = this.handleClick.bind(this);
        }
        handleClick() {
          console.log("SingOut triggered");
        }
        render() {
          return <button onClick={this.handleClick}>SingOut</button>;
        }
      }
      
    2. 公共类字段语法: 如果不喜欢使用 bind 方法,可以使用 公共类字段语法 来正确绑定回调。Create React App 默认启用此语法。

      handleClick = () => {
        console.log("SingOut triggered", this);
      };
      
      <button onClick={this.handleClick}>SingOut</button>
      
    3. 回调中的箭头函数: 可以直接在回调中使用 箭头函数

      handleClick() {
          console.log('SingOut triggered');
      }
      render() {
          return <button onClick={() => this.handleClick()}>SignOut</button>;
      }
      

    注意: 如果回调作为 prop 传递给子组件,那些子组件可能会进行额外的重新渲染。在这些情况下,考虑到性能,首选 .bind()公共类字段语法 方法。

    ⬆ 返回顶部

  3. 如何向事件处理程序或回调传递参数?

    可以使用 箭头函数 包装 事件处理程序 并传递参数:

    <button onClick={() => this.handleClick(id)} />
    

    这等效于调用 .bind

    <button onClick={this.handleClick.bind(this, id)} />
    

    除了这两种方法,还可以向定义为箭头函数的函数传递参数

    <button onClick={this.handleClick(id)} />;
    handleClick = (id) => () => {
      console.log("Hello, your ticket number is", id);
    };
    

    ⬆ 返回顶部

  4. refs 的作用是什么?

    ref 用于返回对元素的引用。在大多数情况下应 避免使用 refs,但当需要直接访问 DOM 元素或组件实例时,它们会很有用。

    ⬆ 返回顶部

  5. 如何创建 refs?

    有两种方法

    1. 这是最近添加的方法。使用 React.createRef() 方法创建 Refs,并通过 ref 属性附加到 React 元素。为了在整个组件中使用 refs,只需在构造函数中将 ref 分配给实例属性。

      class MyComponent extends React.Component {
        constructor(props) {
          super(props);
          this.myRef = React.createRef();
        }
        render() {
          return <div ref={this.myRef} />;
        }
      }
      
    2. 无论 React 版本如何,也可以使用 ref 回调方法。例如,搜索栏组件的输入元素可按如下方式访问:

      class SearchBar extends Component {
        constructor(props) {
          super(props);
          this.txtSearch = null;
          this.state = { term: "" };
          this.setInputSearchRef = (e) => {
            this.txtSearch = e;
          };
        }
        onInputChange(event) {
          this.setState({ term: this.txtSearch.value });
        }
        render() {
          return (
            <input
              value={this.state.term}
              onChange={this.onInputChange.bind(this)}
              ref={this.setInputSearchRef}
            />
          );
        }
      }
      

    也可以使用 闭包 在函数组件中使用 refs注意:也可以使用内联 ref 回调,尽管这不是推荐的方法。

    ⬆ 返回顶部

  6. 什么是 forward refs?

    Ref 转发 是一项功能,允许某些组件接收它们收到的 ref,并将其进一步向下传递给子组件。

    const ButtonElement = React.forwardRef((props, ref) => (
      <button ref={ref} className="CustomButton">
        {props.children}
      </button>
    ));
    
    // 创建对 DOM 按钮的 ref:
    const ref = React.createRef();
    <ButtonElement ref={ref}>{"Forward Ref"}</ButtonElement>;
    

    ⬆ 返回顶部

  7. 在回调 refs 和 findDOMNode() 中,首选哪个选项?

    建议使用 回调 refs 而不是 findDOMNode() API。因为 findDOMNode() 会阻碍 React 未来的某些改进。

    使用 findDOMNode方法:

    class MyComponent extends Component {
      componentDidMount() {
        findDOMNode(this).scrollIntoView();
      }
    
      render() {
        return <div />;
      }
    }
    

    推荐的方法是:

    class MyComponent extends Component {
      constructor(props) {
        super(props);
        this.node = createRef();
      }
      componentDidMount() {
        this.node.current.scrollIntoView();
      }
    
      render() {
        return <div ref={this.node} />;
      }
    }
    

    ⬆ 返回顶部

  8. 为什么字符串 Refs 是遗留的?

    如果您以前使用过 React,可能熟悉一种较旧的 API,其中 ref 属性是一个字符串,如 ref={'textInput'},并且 DOM 节点通过 this.refs.textInput 访问。我们不建议使用它,因为 字符串 refs 存在以下问题,并且被视为遗留问题。字符串 refs 在 React v16 中已移除

    1. 它们 迫使 React 跟踪当前正在执行的组件。这是有问题的,因为它使 react 模块具有状态,从而在 bundle 中复制 react 模块时导致奇怪的错误。

    2. 它们 不可组合 — 如果库在传递的子组件上放置了一个 ref,则用户不能在其上放置另一个 ref。回调 refs 是完全可组合的。

    3. 它们 不适用于 Flow 等静态分析。Flow 无法猜测框架为使字符串 ref 出现在 this.refs 上所做的魔术,以及其类型(可能不同)。回调 refs 对静态分析更友好。

    4. 它在 "render callback" 模式下的工作方式并不像大多数人期望的那样(例如 )

      class MyComponent extends Component {
        renderRow = (index) => {
          // 这不起作用。Ref 将附加到 DataTable 而不是 MyComponent:
          return <input ref={"input-" + index} />;
      
          // 不过这会起作用!回调 refs 很棒。
          return <input ref={(input) => (this["input-" + index] = input)} />;
        };
      
        render() {
          return (
            <DataTable data={this.props.data} renderRow={this.renderRow} />
          );
        }
      }
      

    ⬆ 返回顶部

  9. 组件生命周期的不同阶段是什么?

    组件生命周期有三个不同的生命周期阶段:

    1. 挂载(Mounting): 组件准备好挂载到浏览器 DOM 中。此阶段包括从 constructor()getDerivedStateFromProps()render()componentDidMount() 生命周期方法的初始化。

    2. 更新(Updating): 在这个阶段,组件通过两种方式更新:发送新的 props,以及通过 setState()forceUpdate() 更新 state。此阶段包括 getDerivedStateFromProps()shouldComponentUpdate()render()getSnapshotBeforeUpdate()componentDidUpdate() 生命周期方法。

    3. 卸载(Unmounting): 在最后这个阶段,组件不再需要,并从浏览器 DOM 中卸载。此阶段包括 componentWillUnmount() 生命周期方法。

    值得一提的是,React 在内部应用 DOM 更改时具有阶段的概念。它们的分离如下:

    1. 渲染(Render): 组件将无副作用地渲染。这适用于纯组件,在此阶段,React 可以暂停、中止或重新启动渲染。

    2. 预提交(Pre-commit): 在组件实际将更改应用到 DOM 之前,有一个时刻允许 React 通过 getSnapshotBeforeUpdate() 从 DOM 读取数据。

    3. 提交(Commit): React 与 DOM 协同工作,并分别执行最终的生命周期:挂载时的 componentDidMount()、更新时的 componentDidUpdate() 和卸载时的 componentWillUnmount()

    React 16.3+ 阶段(或 交互式版本

    phases 16.4+

    React 16.3 之前

    phases 16.2

    ⬆ 返回顶部

  10. React 的生命周期方法有哪些?

    React 16.3 之前

    • componentWillMount: 在渲染前执行,用于在根组件中进行应用级配置。
    • componentDidMount: 在首次渲染后执行,所有 AJAX 请求、DOM 或 state 更新以及设置事件监听器都应在此处发生。
    • componentWillReceiveProps: 在特定 prop 更新时执行,以触发状态转换。
    • shouldComponentUpdate: 确定组件是否将被更新。默认返回 true。如果确定组件在 state 或 props 更新后不需要渲染,可以返回 false 值。这是提高性能的好地方,因为它允许在组件收到新 prop 时阻止重新渲染。
    • componentWillUpdate: 在组件因 props 和 state 变化(经 shouldComponentUpdate() 确认返回 true)而重新渲染前执行。
    • componentDidUpdate: 主要用于响应 prop 或 state 变化来更新 DOM。
    • componentWillUnmount: 用于取消任何未完成的网络请求,或移除与组件关联的所有事件监听器。

    React 16.3+

    • getDerivedStateFromProps: 在调用 render() 之前立即调用,并且在 每次 渲染时都会调用。这存在于一些罕见的用例中,需要派生状态。值得阅读 if you need derived state
    • componentDidMount: 在首次渲染后执行,所有 AJAX 请求、DOM 或 state 更新以及设置事件监听器都应在此处发生。
    • shouldComponentUpdate: 确定组件是否将被更新。默认返回 true。如果确定组件在 state 或 props 更新后不需要渲染,可以返回 false 值。这是提高性能的好地方,因为它允许在组件收到新 prop 时阻止重新渲染。
    • getSnapshotBeforeUpdate: 在渲染输出提交到 DOM 之前立即执行。此方法返回的任何值都将传递给 componentDidUpdate()。这对于从 DOM 捕获信息(即滚动位置)很有用。
    • componentDidUpdate: 主要用于响应 prop 或 state 变化来更新 DOM。如果 shouldComponentUpdate() 返回 false,则不会触发此方法。
    • componentWillUnmount 用于取消任何未完成的网络请求,或移除与组件关联的所有事件监听器。

    ⬆ 返回顶部

  11. 如何为 HOC 组件创建 props 代理?

    可以使用 props 代理 模式添加/编辑传递给组件的 props,如下所示:

    function HOC(WrappedComponent) {
      return class Test extends Component {
        render() {
          const newProps = {
            title: "New Header",
            footer: false,
            showFeatureX: false,
            showFeatureY: true,
          };
    
          return <WrappedComponent {...this.props} {...newProps} />;
        }
      };
    }
    

    ⬆ 返回顶部

  12. 什么是 context?

    Context 提供了一种在组件树中传递数据的方式,无需在每个级别手动传递 props。

    例如,已认证用户、区域设置首选项、UI 主题等需要被应用程序中的许多组件访问。

    const { Provider, Consumer } = React.createContext(defaultValue);
    

    ⬆ 返回顶部

  13. 在 super 构造函数中使用 props 参数的目的是什么?

    子类构造函数在调用 super() 方法之前不能使用 this 引用。ES6 子类也是如此。将 props 参数传递给 super() 调用的主要原因是在子构造函数中访问 this.props

    传递 props:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
    
        console.log(this.props); // 打印 { name: 'John', age: 42 }
      }
    }
    

    不传递 props:

    class MyComponent extends React.Component {
      constructor(props) {
        super();
    
        console.log(this.props); // 打印 undefined
    
        // 但 props 参数仍然可用
        console.log(props); // 打印 { name: 'John', age: 42 }
      }
    
      render() {
        // 构造函数外部没有区别
        console.log(this.props); // 打印 { name: 'John', age: 42 }
      }
    }
    

    上面的代码片段表明,this.props 仅在构造函数内部有所不同。在构造函数外部是相同的。

    ⬆ 返回顶部

  14. 如何使用动态键名设置 state?

    如果使用 ES6 或 Babel 转译器来转换 JSX 代码,那么可以使用 计算属性名 来实现这一点。

    handleInputChange(event) {
      this.setState({ [event.target.id]: event.target.value })
    }
    

    ⬆ 返回顶部

  15. 组件每次渲染时函数被调用的常见错误是什么?

    必须确保在将函数作为参数传递时没有调用该函数。

    render() {
      // 错误:handleClick 被调用而不是作为引用传递!
      return <button onClick={this.handleClick()}>{'Click Me'}</button>
    }
    

    相反,应传递函数本身,不带括号:

    render() {
      // 正确:handleClick 作为引用传递!
      return <button onClick={this.handleClick}>{'Click Me'}</button>
    }
    

    ⬆ 返回顶部

  16. React v16 中的错误边界是什么?

    注意: 错误边界在 React 16 中引入,在当前 React 版本(18/19)中仍然有效。

    错误边界 是一种组件,它可以捕获其子组件树中任何位置的 JavaScript 错误,记录这些错误,并显示回退 UI,而不是崩溃的组件树。

    如果类组件定义了以下生命周期方法,它就会成为错误边界:

    • static getDerivedStateFromError(error) - 用于渲染回退 UI
    • componentDidCatch(error, info) - 用于记录错误信息
    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error) {
        // 更新状态,以便下一次渲染将显示回退 UI
        return { hasError: true };
      }
    
      componentDidCatch(error, info) {
        // 将错误记录到错误报告服务
        console.error('Error caught by boundary:', error, info);
        logErrorToMyService(error, info);
      }
    
      render() {
        if (this.state.hasError) {
          // 渲染自定义回退 UI
          return <h1>Something went wrong.</h1>;
        }
        return this.props.children;
      }
    }
    

    使用方法:

    <ErrorBoundary>
      <MyWidget />
    </ErrorBoundary>
    

    注意: 错误边界目前仅适用于类组件。目前还没有对应的 Hook,但 React 19 中的 use() Hook 提供了一些错误处理能力。

    ⬆ 返回顶部

  17. React v15 中如何处理错误边界?

    ⚠️ 遗留: 此问题仅用于历史背景。React v15 已经非常过时(2016 年发布)。

    React v15 使用 unstable_handleError 方法提供了对 错误边界 的非常基本的支持。这是一个实验性特性,后来在 React v16 中被重新设计并更名为 componentDidCatch

    现代错误边界(React 16+):

    • 使用 static getDerivedStateFromError(error) 进行 UI 回退
    • 使用 componentDidCatch(error, info) 进行日志记录
    • 在服务器端和客户端渲染中一致工作

    ⬆ 返回顶部

  18. react-dom 的 render 方法的作用是什么?

此方法用于将 React 元素渲染到提供的容器中的 DOM 中,并返回对该组件的引用。如果该 React 元素之前已渲染到容器中,它将对其执行更新,并且仅在必要时修改 DOM 以反映最新的更改。

```
ReactDOM.render(element, container, [callback])
```

如果提供了可选的回调函数,它将在组件渲染或更新后执行。

**[⬆ 返回顶部](#table-of-contents)**
  1. 在构造函数中使用 setState() 会发生什么?

    当你使用 setState() 时,除了将值分配给 state 对象外,React 还会重新渲染该组件及其所有子组件。你会收到如下错误:Can only update a mounted or mounting component.(只能更新已挂载或正在挂载的组件。)因此,我们需要在构造函数内部使用 this.state 来初始化变量。

    ⬆ 返回顶部

  2. componentWillMount() 方法中使用 setState() 好吗?

    ⚠️ 已弃用: componentWillMount() 在 React 17+ 中已被移除。这个问题仅与 legacy React 应用程序相关。

    历史背景: 虽然在技术上可以在 componentWillMount() 内部使用 setState(),但这个生命周期方法现在已被弃用和移除,原因如下:

    • 它会导致服务器端渲染出现问题
    • 对于异步操作的正确位置造成了混淆
    • 与 React 的并发渲染特性存在冲突

    现代替代方案: 在类组件中,使用 componentDidMount() 处理副作用和异步初始化:

    componentDidMount() {
      axios.get(`api/todos`)
        .then((result) => {
          this.setState({
            messages: [...result.data]
          })
        })
    }
    

    ⬆ 返回顶部

  3. 在初始 state 中使用 props 会发生什么?

    如果组件的 props 发生了变化,但组件没有被重新渲染,新的 prop 值将永远不会显示,因为构造函数永远不会更新组件的当前 state。从 props 初始化 state 的操作只在组件首次创建时运行。

    下面的组件不会显示更新后的输入值:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          records: [],
          inputValue: this.props.inputValue,
        };
      }
    
      render() {
        return <div>{this.state.inputValue}</div>;
      }
    }
    

    在 render 方法中直接使用 props 会更新值:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          record: [],
        };
      }
    
      render() {
        return <div>{this.props.inputValue}</div>;
      }
    }
    

    ⬆ 返回顶部

  4. 如何在 React 中使用装饰器?

    你可以为类组件添加“装饰”,这相当于将组件传递到一个函数中。装饰器是一种灵活且可读性强的修改组件功能的方式。

    @setTitle("Profile")
    class Profile extends React.Component {
      //....
    }
    
    /*
      title 是将被设置为文档标题的字符串
      WrappedComponent 是当装饰器直接放在组件类上方时(如上面示例所示),装饰器将接收的内容
    */
    const setTitle = (title) => (WrappedComponent) => {
      return class extends React.Component {
        componentDidMount() {
          document.title = title;
        }
    
        render() {
          return <WrappedComponent {...this.props} />;
        }
      };
    };
    

    注意: 装饰器是一项未纳入 ES7 的特性,但目前是一个 stage 2 提案。

    ⬆ 返回顶部

  5. 什么是 CRA 及其优点?

    ⚠️ 已过时: Create React App (CRA) 不再积极维护,截至 2024+ 年,不推荐用于新项目。

    现代替代方案: 改用 Vite(用于 SPA)或 Next.js(用于全栈/SSR)。请参阅本答案末尾的推荐设置命令。


    历史背景:

    create-react-app CLI 工具允许你快速创建和运行 React 应用程序,无需配置步骤。

    让我们使用 CRA 创建 Todo 应用:

    # 安装
    $ npm install -g create-react-app
    
    # 创建新项目
    $ create-react-app todo-app
    $ cd todo-app
    
    # 构建、测试和运行
    $ npm run build
    $ npm run test
    $ npm start
    

    它包含构建 React 应用所需的一切:

    1. 对 React、JSX、ES6 和 Flow 语法的支持。
    2. 超越 ES6 的语言扩展,如对象展开运算符。
    3. 自动添加前缀的 CSS,因此你不需要 -webkit- 或其他前缀。
    4. 一个快速的交互式单元测试运行器,内置覆盖率报告支持。
    5. 一个实时开发服务器,会对常见错误发出警告。
    6. 一个构建脚本,用于将 JS、CSS 和图像打包用于生产环境,并带有哈希值和源映射。

    现代替代方案(2026 年推荐):

    1. Vite(最适合 SPA):

    npm create vite@latest my-app -- --template react
    cd my-app
    npm install
    npm run dev
    
    • 闪电般快速的 HMR(热模块替换)
    • 基于现代 ESM 的开发
    • 使用 Rollup 进行优化的生产构建

    2. Next.js(最适合全栈/SSR):

    npx create-next-app@latest my-app
    cd my-app
    npm run dev
    
    • 服务器端渲染和 App Router
    • 服务器组件和服务器操作
    • 内置路由、API 路由和优化

    3. Remix(全栈替代方案):

    • 以 Web 基础为优先的方法
    • 出色的性能和开发者体验
    • 渐进式增强

    ⬆ 返回顶部

  6. 组件挂载时生命周期方法的调用顺序是什么?

    当组件实例被创建并插入到 DOM 中时,生命周期方法会按以下顺序调用。

    1. constructor()
    2. static getDerivedStateFromProps()
    3. render()
    4. componentDidMount()

    ⬆ 返回顶部

  7. React v16 中哪些生命周期方法将要被弃用?

    ⚠️ 已完全弃用: 这些生命周期方法已被弃用,并已从 React 17+ 中移除。

    以下生命周期方法因不安全的编码实践和异步渲染问题而被弃用:

    1. componentWillMount() - 在 React 17 中已移除
    2. componentWillReceiveProps() - 在 React 17 中已移除
    3. componentWillUpdate() - 在 React 17 中已移除

    时间线:

    • React 16.3:方法使用 UNSAFE_ 前缀作为别名
    • React 17+:未加前缀的版本被完全移除
    • 当前(React 18/19):仅存在 UNSAFE_ 版本(不推荐使用)

    现代替代方案:

    已弃用方法 现代替代方案
    componentWillMount() constructor()componentDidMount()
    componentWillReceiveProps() static getDerivedStateFromProps()componentDidUpdate()
    componentWillUpdate() getSnapshotBeforeUpdate() + componentDidUpdate()

    最佳实践: 使用带有 Hooks 的函数组件代替:

    • useEffect() 用于副作用
    • useState() 用于状态管理
    • useMemo()/useCallback() 用于优化

    ⬆ 返回顶部

  8. getDerivedStateFromProps() 生命周期方法的作用是什么?

    新的静态 getDerivedStateFromProps() 生命周期方法在组件实例化后以及重新渲染前被调用。它可以返回一个对象来更新 state,或者返回 null 以指示新的 props 不需要任何 state 更新。

    class MyComponent extends React.Component {
      static getDerivedStateFromProps(props, state) {
        // ...
      }
    }
    

    此生命周期方法与 componentDidUpdate() 一起涵盖了 componentWillReceiveProps() 的所有使用场景。

    ⬆ 返回顶部

  9. getSnapshotBeforeUpdate() 生命周期方法的作用是什么?

    新的 getSnapshotBeforeUpdate() 生命周期方法在 DOM 更新之前被调用。此方法的返回值将作为第三个参数传递给 componentDidUpdate()

    class MyComponent extends React.Component {
      getSnapshotBeforeUpdate(prevProps, prevState) {
        // ...
      }
    }
    

    此生命周期方法与 componentDidUpdate() 一起涵盖了 componentWillUpdate() 的所有使用场景。

    ⬆ 返回顶部

  10. 命名组件的推荐方式是什么?

    建议通过引用来命名组件,而不是使用 displayName

    使用 displayName 命名组件:

    export default React.createClass({
      displayName: "TodoApp",
      // ...
    });
    

    推荐的方法:

    export default class TodoApp extends React.Component {
      // ...
    }
    

    还有

    const TodoApp = () => {
      //...
    };
    export default TodoApp;
    

    ⬆ 返回顶部

  11. 组件类中方法的推荐排序是什么?

    从“挂载”到“渲染阶段”的推荐方法排序:

    1. static 方法
    2. constructor()
    3. getChildContext()
    4. componentWillMount()
    5. componentDidMount()
    6. componentWillReceiveProps()
    7. shouldComponentUpdate()
    8. componentWillUpdate()
    9. componentDidUpdate()
    10. componentWillUnmount()
    11. 点击处理程序或事件处理程序,如 onClickSubmit()onChangeDescription()
    12. 用于渲染的 getter 方法,如 getSelectReason()getFooterContent()
    13. 可选的渲染方法,如 renderNavigation()renderProfilePicture()
    14. render()

    ⬆ 返回顶部

  12. 为什么需要向 setState() 传递一个函数?

    其原因是 setState() 是一个异步操作。React 为了性能会批处理状态更改,因此在调用 setState() 后,状态可能不会立即改变。这意味着你不应该在调用 setState() 时依赖当前状态,因为你无法确定该状态会是什么。解决方案是向 setState() 传递一个函数,并将先前的状态作为参数。通过这样做,你可以避免由于 setState() 的异步特性,在访问时获取到旧状态值的问题。

    假设初始 count 值为零。经过三次连续的递增操作后,该值只会递增 1。

    // 假设 this.state.count === 0
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    this.setState({ count: this.state.count + 1 });
    // this.state.count === 1,而不是 3
    

    如果我们向 setState() 传递一个函数,count 会正确递增。

    this.setState((prevState, props) => ({
      count: prevState.count + props.increment,
    }));
    // this.state.count === 3,符合预期
    

    (或)

    为什么 setState() 优先使用函数而不是对象?

    React 可能会将多个 setState() 调用批处理为单个更新以提高性能。由于 this.propsthis.state 可能会异步更新,因此你不应该依赖它们的值来计算下一个状态。

    下面的计数器示例将无法按预期更新:

    // 错误
    this.setState({
      counter: this.state.counter + this.props.increment,
    });
    

    推荐的方法是调用 setState() 时传入函数而不是对象。该函数将接收前一个状态作为第一个参数,以及应用更新时的 props 作为第二个参数。

    // 正确
    this.setState((prevState, props) => ({
      counter: prevState.counter + props.increment,
    }));
    

    ⬆ 返回顶部

  13. 为什么 isMounted() 是一种反模式,正确的解决方案是什么?

    isMounted() 的主要用例是避免在组件已卸载后调用 setState(),因为这会发出警告。

    if (this.isMounted()) {
      this.setState({...})
    }
    

    在调用 setState() 之前检查 isMounted() 确实可以消除警告,但这也违背了警告的初衷。使用 isMounted() 是一种代码异味,因为你检查的唯一原因是你认为在组件卸载后可能仍持有引用。

    最佳解决方案是找出可能在组件卸载后调用 setState() 的地方,并修复它们。这种情况最常见于回调,当组件正在等待某些数据,而在数据到达之前就已卸载。理想情况下,任何回调都应该在 componentWillUnmount() 中、卸载之前被取消。

    ⬆ 返回顶部

  14. constructor 和 getInitialState 的区别是什么?

    使用 ES6 类时,应在构造函数中初始化 state;使用 React.createClass() 时,应使用 getInitialState() 方法。

    使用 ES6 类:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          /* 初始状态 */
        };
      }
    }
    

    使用 React.createClass()

    const MyComponent = React.createClass({
      getInitialState() {
        return {
          /* 初始状态 */
        };
      },
    });
    

    注意: React.createClass() 已被弃用,并在 React v16 中移除。请改用普通的 JavaScript 类。

    ⬆ 返回顶部

  15. 能否在不调用 setState 的情况下强制组件重新渲染?

    默认情况下,当组件的 state 或 props 发生变化时,组件会重新渲染。如果你的 render() 方法依赖于其他一些数据,你可以通过调用 forceUpdate() 告诉 React 该组件需要重新渲染。

    component.forceUpdate(callback);
    

    建议避免所有 forceUpdate() 的使用,并且只在 render() 中读取 this.propsthis.state

    ⬆ 返回顶部

  16. 在 React 的 ES6 类中,super()super(props) 有什么区别?

    当你想在 constructor() 中访问 this.props 时,你应该将 props 传递给 super() 方法。

    使用 super(props)

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        console.log(this.props); // { name: 'John', ... }
      }
    }
    

    使用 super()

    class MyComponent extends React.Component {
      constructor(props) {
        super();
        console.log(this.props); // undefined
      }
    }
    

    constructor() 外部,两者的 this.props 值相同。

    ⬆ 返回顶部

  17. setState()replaceState() 方法的区别是什么?

    当你使用 setState() 时,当前状态和先前状态会合并。replaceState() 会抛弃当前状态,并用你提供的内容完全替换它。通常使用 setState(),除非你确实因为某些原因需要移除所有先前的键。你也可以在 setState() 中将状态设置为 false/null,而不是使用 replaceState()⬆ 返回顶部

  18. 如何监听 state 的变化?

    当 state 发生变化时,componentDidUpdate 生命周期方法会被调用。你可以将提供的 state 和 props 值与当前的 state 和 props 进行比较,以确定是否有有意义的变化。

    componentDidUpdate(object prevProps, object prevState)
    

    注意: ReactJS 的早期版本也使用 componentWillUpdate(object nextProps, object nextState) 来处理状态变化。它在最新版本中已被弃用。

⬆ 返回顶部

  1. 在 React state 中删除数组元素的推荐方法是什么?

    更好的方法是使用 Array.prototype.filter() 方法。

    例如,让我们创建一个 removeItem() 方法来更新状态。

    removeItem(index) {
      this.setState({
        data: this.state.data.filter((item, i) => i !== index)
      })
    }
    

⬆ 返回顶部

  1. 是否可以使用 React 而不渲染 HTML?

    可以。以下是可能的选项:

    render() {
      return false
    }
    
    render() {
      return true
    }
    
    render() {
      return null
    }
    

    React 版本 >=16.0.0:

    render() {
      return []
    }
    
    render() {
      return ""
    }
    

    React 版本 >=16.2.0:

    render() {
      return <React.Fragment></React.Fragment>
    }
    
    render() {
      return <></>
    }
    

    React 版本 >=18.0.0:

    render() {
      return undefined
    }
    

⬆ 返回顶部

  1. 更新 state 中对象的可能方法有哪些?

    1. 调用 setState() 并传入一个对象以合并到 state 中:

      • 使用 Object.assign() 创建对象的副本:

        const user = Object.assign({}, this.state.user, { age: 42 });
        this.setState({ user });
        
      • 使用展开运算符

        const user = { ...this.state.user, age: 42 };
        this.setState({ user });
        
    2. 调用 setState() 并传入一个函数:

      this.setState((prevState) => ({
        user: {
          ...prevState.user,
          age: 42,
        },
      }));
      

⬆ 返回顶部

  1. create-react-app 中包含 polyfills 的方法有哪些?

    有多种方法可以在 create-react-app 中包含 polyfills,

    1. core-js 手动导入:

      创建一个名为(类似)polyfills.js 的文件,并将其导入到根目录的 index.js 文件中。运行 npm install core-jsyarn add core-js,然后导入你特定需要的功能。

      import "core-js/fn/array/find";
      import "core-js/fn/array/includes";
      import "core-js/fn/number/is-nan";
      
    2. 使用 Polyfill 服务:

      通过将以下行添加到 index.html,使用 polyfill.io CDN 来检索自定义的、特定于浏览器的 polyfills:

      <script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=default,Array.prototype.includes"></script>
      

      在上面的脚本中,我们必须显式请求 Array.prototype.includes 功能,因为它不包含在默认功能集中。

⬆ 返回顶部

  1. 如何在 create-react-app 中使用 https 而非 http?

    只需使用 HTTPS=true 配置即可。你可以编辑 package.json 的 scripts 部分:

    "scripts": {
      "start": "set HTTPS=true && react-scripts start"
    }
    

    或者直接运行 set HTTPS=true && npm start

⬆ 返回顶部

  1. 如何在 create-react-app 中避免使用相对路径导入?

    在项目根目录创建一个名为 .env 的文件,并写入导入路径:

    NODE_PATH=src/app
    

    之后重启开发服务器。现在你应该可以导入 src/app 内部的任何内容,而无需使用相对路径。

⬆ 返回顶部

  1. 如何每秒更新一个组件?

    你需要使用 setInterval() 来触发更改,但也需要在组件卸载时清除计时器,以防止错误和内存泄漏。

    componentDidMount() {
      this.interval = setInterval(() => this.setState({ time: Date.now() }), 1000)
    }
    
    componentWillUnmount() {
      clearInterval(this.interval)
    }
    

⬆ 返回顶部

  1. 为什么组件构造函数只被调用一次?

    React 的_协调_算法假设,在没有相反信息的情况下,如果一个自定义组件在后续渲染中出现在相同位置,它就是之前的同一个组件,因此会重用之前的实例而不是创建新实例。

⬆ 返回顶部

  1. 如何在 React 中定义常量?

    你可以使用 ES7 的 static 字段来定义常量。

    class MyComponent extends React.Component {
      static DEFAULT_PAGINATION = 10;
    }
    

⬆ 返回顶部

  1. 如何在 React 中以编程方式触发点击事件?

    你可以使用 ref 属性通过回调获取底层 HTMLInputElement 对象的引用,将该引用存储为类属性,然后在事件处理程序中使用 HTMLElement.click 方法触发点击。

    这可以分两步完成:

    1. 在 render 方法中创建 ref:

      <input ref={(input) => (this.inputElement = input)} />
      
    2. 在事件处理程序中应用点击事件:

      this.inputElement.click();
      

⬆ 返回顶部

  1. 如何进行 AJAX 调用,以及应该在哪个组件生命周期方法中进行 AJAX 调用?

    你可以使用诸如 Axios、jQuery AJAX 以及浏览器内置的 fetch 等 AJAX 库。你应该在 componentDidMount() 生命周期方法中获取数据。这样你就可以在检索到数据时使用 setState() 来更新组件。

    例如,从 API 获取员工列表并设置本地状态:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          employees: [],
          error: null,
        };
      }
    
      componentDidMount() {
        fetch("https://api.example.com/items")
          .then((res) => res.json())
          .then(
            (result) => {
              this.setState({
                employees: result.employees,
              });
            },
            (error) => {
              this.setState({ error });
            }
          );
      }
    
      render() {
        const { error, employees } = this.state;
        if (error) {
          return <div>Error: {error.message}</div>;
        } else {
          return (
            <ul>
              {employees.map((employee) => (
                <li key={employee.name}>
                  {employee.name}-{employee.experience}
                </li>
              ))}
            </ul>
          );
        }
      }
    }
    

⬆ 返回顶部

  1. 什么是 render props?

    Render Props 是一种通过值为函数的 prop 在组件之间共享代码的简单技术。下面的组件使用 render prop,它返回一个 React 元素。

    <DataProvider render={(data) => <h1>{`Hello ${data.target}`}</h1>} />
    

    像 React Router 和 DownShift 这样的库都在使用这种模式。

⬆ 返回顶部

  1. 如何在加载时调度一个 action?

    你可以在 componentDidMount() 方法中调度一个 action,并在 render() 方法中验证数据。

    class App extends Component {
      componentDidMount() {
        this.props.fetchData();
      }
    
      render() {
        return this.props.isLoaded ? (
          <div>{"Loaded"}</div>
        ) : (
          <div>{"Not Loaded"}</div>
        );
      }
    }
    
    const mapStateToProps = (state) => ({
      isLoaded: state.isLoaded,
    });
    
    const mapDispatchToProps = { fetchData };
    
    export default connect(mapStateToProps, mapDispatchToProps)(App);
    

⬆ 返回顶部

  1. 如何使用 React Redux 中的 connect()

    要在容器中使用 store,你需要遵循两个步骤:

    1. 使用 mapStateToProps() 它将 store 中的状态变量映射到你指定的 props。

    2. 将上述 props 连接到你的容器: mapStateToProps 函数返回的对象会被连接到容器。你可以从 react-redux 中导入 connect()

      import React from "react";
      import { connect } from "react-redux";
      
      class App extends React.Component {
        render() {
          return <div>{this.props.containerData}</div>;
        }
      }
      
      function mapStateToProps(state) {
        return { containerData: state.data };
      }
      
      export default connect(mapStateToProps)(App);
      

⬆ 返回顶部

  1. Redux connect 装饰器中的 at 符号(@)有什么作用?

    @ 符号实际上是一个 JavaScript 表达式,用于表示装饰器。_装饰器_使得在设计时对类和属性进行注解和修改成为可能。

    让我们看一个不使用装饰器和使用装饰器设置 Redux 的例子。

    • 不使用装饰器:

      import React from "react";
      import * as actionCreators from "./actionCreators";
      import { bindActionCreators } from "redux";
      import { connect } from "react-redux";
      
      function mapStateToProps(state) {
        return { todos: state.todos };
      }
      
      function mapDispatchToProps(dispatch) {
        return { actions: bindActionCreators(actionCreators, dispatch) };
      }
      
      class MyApp extends React.Component {
        // ...在这里定义你的主应用
      }
      
      export default connect(mapStateToProps, mapDispatchToProps)(MyApp);
      
    • 使用装饰器:

      import React from "react";
      import * as actionCreators from "./actionCreators";
      import { bindActionCreators } from "redux";
      import { connect } from "react-redux";
      
      function mapStateToProps(state) {
        return { todos: state.todos };
      }
      
      function mapDispatchToProps(dispatch) {
        return { actions: bindActionCreators(actionCreators, dispatch) };
      }
      
      @connect(mapStateToProps, mapDispatchToProps)
      export default class MyApp extends React.Component {
        // ...在这里定义你的主应用
      }
      

    上述示例除了装饰器的使用外几乎相同。装饰器语法尚未内置于任何 JavaScript 运行时中,它仍然是实验性的,并且可能会发生变化。你可以使用 Babel 来支持装饰器。

⬆ 返回顶部

  1. 如何在 create-react-app 应用中使用 TypeScript?

    从 react-scripts@3.3.0+ 版本开始,你现在可以通过在创建命令后附加 `--template [template-name]` 来选择从模板启动新应用。如果你不选择模板,它将使用基础模板创建你的项目。请记住,模板的命名格式始终为 `cra-template-[template-name]`,这里你只需要填写 `[template-name]` 部分。
    
    可以通过在创建命令后附加 `--template typescript` 来在你的项目中使用 TypeScript。
    
     ```bash
     npx create-react-app my-app --template typescript
     ```
    
    但是如果你使用的 React Scripts 版本在 react-scripts@2.1.0 和 react-scripts@3.2.x 之间,那么有对 TypeScript 的内置支持。即,`create-react-app` 现在原生支持 TypeScript。你只需按如下方式传递 `--typescript` 选项:
    
     ```bash
     npx create-react-app my-app --typescript
    
     # 或者
    
     yarn create react-app my-app --typescript
     ```
    
     而对于更低版本的 react scripts,只需在创建新项目时提供 `--scripts-version` 选项为 `react-scripts-ts`。`react-scripts-ts` 是一组调整,用于采用标准的 `create-react-app` 项目流水线并将 TypeScript 融入其中。
    
     现在项目布局应该如下所示:
    
     ```
     my-app/
     ├─ .gitignore
     ├─ images.d.ts
     ├─ node_modules/
     ├─ public/
     ├─ src/
     │  └─ ...
     ├─ package.json
     ├─ tsconfig.json
     ├─ tsconfig.prod.json
     ├─ tsconfig.test.json
     └─ tslint.json
     ```
    

    ⬆ 返回顶部

  2. statics 对象在 React 的 ES6 类中有效吗?

    无效,statics 仅对 React.createClass() 有效:

    someComponent = React.createClass({
      statics: {
        someMethod: function () {
          // ..
        },
      },
    });
    

    但是你可以在 ES6+ 类中像下面这样编写静态属性和方法,

    class Component extends React.Component {
      static propTypes = {
        // ...
      };
    
      static someMethod() {
        // ...
      }
    }
    

    或者在类外部编写它们,如下所示,

    class Component extends React.Component {
       ....
    }
    
    Component.propTypes = {...}
    Component.someMethod = function(){....}
    

⬆ 返回顶部

  1. 为什么不推荐使用内联 ref 回调或函数?

    如果 ref 回调被定义为内联函数,它会在更新期间被调用两次,第一次传入 null,然后再次传入 DOM 元素。这是因为每次渲染都会创建一个新的函数实例,所以 React 需要清除旧的 ref 并设置新的 ref。

    class UserForm extends Component {
      handleSubmit = () => {
        console.log("Input Value is: ", this.input.value);
      };
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <input type="text" ref={(input) => (this.input = input)} /> // 在 handle submit 中访问 DOM 输入
            <button type="submit">Submit</button>
          </form>
        );
      }
    }
    

    但我们期望 ref 回调只在组件挂载时被调用一次。一个快速的解决方法是使用 ES7 类属性语法来定义该函数:

    class UserForm extends Component {
      handleSubmit = () => {
        console.log("Input Value is: ", this.input.value);
      };
    
      setSearchInput = (input) => {
        this.input = input;
      };
    
      render() {
        return (
          <form onSubmit={this.handleSubmit}>
            <input type="text" ref={this.setSearchInput} /> // 在 handle submit 中访问 DOM 输入
            <button type="submit">Submit</button>
          </form>
        );
      }
    }
    

⬆ 返回顶部

  1. 什么是 HOC 工厂实现?

    在 React 中有两种主要的 HOC(高阶组件)实现方式。

    1. 属性代理(Props Proxy,PP)和
    2. 反向继承(Inheritance Inversion,II)。

    但它们采用不同的方法来操作_被包装组件(WrappedComponent)_。

    属性代理(Props Proxy)

    在这种方法中,HOC 的 render 方法返回一个被包装组件类型的 React 元素。我们还会传递 HOC 接收到的 props,因此得名属性代理

    function ppHOC(WrappedComponent) {
      return class PP extends React.Component {
        render() {
          return <WrappedComponent {...this.props} />;
        }
      };
    }
    

    反向继承(Inheritance Inversion)

    在这种方法中,返回的 HOC 类(Enhancer)继承自被包装组件(WrappedComponent)。它被称为反向继承,是因为不是被包装组件继承某个增强器类,而是增强器被动地继承被包装组件。这样它们之间的关系看起来是反向的

    function iiHOC(WrappedComponent) {
      return class Enhancer extends WrappedComponent {
        render() {
          return super.render();
        }
      };
    }
    

⬆ 返回顶部

  1. 如何在 React 类中使用类字段声明语法?

    使用类字段声明可以使 React 类组件更加简洁。你可以无需使用构造函数来初始化本地状态,并通过使用箭头函数来声明类方法,而无需额外绑定它们。

    让我们以一个计数器示例来演示不使用构造函数的状态类字段声明和无需绑定的方法,

    class Counter extends Component {
      state = { value: 0 };
    
      handleIncrement = () => {
        this.setState((prevState) => ({
          value: prevState.value + 1,
        }));
      };
    
      handleDecrement = () => {
        this.setState((prevState) => ({
          value: prevState.value - 1,
        }));
      };
    
      render() {
        return (
          <div>
            {this.state.value}
    
            <button onClick={this.handleIncrement}>+</button>
            <button onClick={this.handleDecrement}>-</button>
          </div>
        );
      }
    }
    

⬆ 返回顶部

  1. 为什么事件处理程序不需要错误边界?

    错误边界不会捕获事件处理程序内部的错误。

    React 不需要错误边界来从事件处理程序中的错误中恢复。与 render 方法和生命周期方法不同,事件处理程序不会在渲染期间发生。因此,如果它们抛出错误,React 仍然知道屏幕上应该显示什么。

    如果你需要在事件处理程序内部捕获错误,请使用常规的 JavaScript try/catch 语句:

    class MyComponent extends React.Component {
      constructor(props) {
        super(props);
        this.state = { error: null };
        this.handleClick = this.handleClick.bind(this);
      }
    
      handleClick() {
        try {
          // 执行可能会抛出错误的操作
        } catch (error) {
          this.setState({ error });
        }
      }
    
      render() {
        if (this.state.error) {
          return <h1>捕获到一个错误。</h1>;
        }
        return <button onClick={this.handleClick}>点击我</button>;
      }
    }
    

    请注意,上面的示例演示的是常规 JavaScript 行为,并未使用错误边界。

⬆ 返回顶部

  1. try catch 块和错误边界有什么区别?

    try catch 块适用于命令式代码,而错误边界用于声明式代码在屏幕上的渲染。

    例如,try catch 块用于下面的命令式代码:

    try {
      showButton();
    } catch (error) {
      // ...
    }
    

    而错误边界包装声明式代码如下:

    <ErrorBoundary>
      <MyComponent />
    </ErrorBoundary>
    

    因此,如果在 componentDidUpdate 方法中发生由树深处的某个 setState 引起的错误,它仍然会正确地传播到最近的错误边界。

⬆ 返回顶部

  1. 类组件需要定义的必要方法是什么?

    render() 方法是类组件中唯一必需的方法。即,除 render 方法外,所有其他方法对于类组件都是可选的。

⬆ 返回顶部

  1. render 方法可能的返回类型有哪些?

    以下是 render 方法使用和返回的类型列表:

    1. React 元素: 指示 React 渲染 DOM 节点的元素。它包括 html 元素(如 <div/>)和用户定义的元素。
    2. 数组和片段: 返回多个元素以数组形式渲染,或使用片段(Fragments)包装多个元素。
    3. Portals( portals): 将子元素渲染到不同的 DOM 子树中。
    4. 字符串和数字: 将字符串和数字都渲染为 DOM 中的文本节点。
    5. 布尔值或 null: 不渲染任何内容,但这些类型用于有条件地渲染内容。

⬆ 返回顶部

  1. 构造函数的主要目的是什么?

    构造函数主要用于两个目的:

    1. 通过将对象分配给 this.state 来初始化本地状态。
    2. 将事件处理程序方法绑定到实例。 例如,下面的代码涵盖了上述两种情况:
    constructor(props) {
      super(props);
      // 不要在这里调用 this.setState()!
      this.state = { counter: 0 };
      this.handleClick = this.handleClick.bind(this);
    }
    

⬆ 返回顶部

  1. React 组件必须定义构造函数吗?

    不,不是必须的。即,如果你不初始化状态并且不绑定方法,就不需要为 React 组件实现构造函数。

⬆ 返回顶部

  1. 为什么不应该在 componentWillUnmount 中调用 setState?

    你不应该在 componentWillUnmount() 中调用 setState(),因为一旦组件实例被卸载,它将永远不会被重新挂载。

⬆ 返回顶部

  1. getDerivedStateFromError 的目的是什么?

    这个生命周期方法在后代组件抛出错误后被调用。它接收抛出的错误作为参数,并应返回一个值来更新状态。

    该生命周期方法的签名如下:

    static getDerivedStateFromError(error)
    

    让我们以错误边界的用例为例,使用上述生命周期方法进行演示:

    class ErrorBoundary extends React.Component {
      constructor(props) {
        super(props);
        this.state = { hasError: false };
      }
    
      static getDerivedStateFromError(error) {
        // 更新状态,以便下一次渲染将显示回退 UI。
        return { hasError: true };
      }
    
      render() {
        if (this.state.hasError) {
          // 你可以渲染任何自定义的回退 UI
          return <h1>出错了。</h1>;
        }
    
        return this.props.children;
      }
    }
    

⬆ 返回顶部

  1. 组件重新渲染时方法的调用顺序是什么?

    更新可能由 props 或 state 的更改引起。当组件正在重新渲染时,以下方法按以下顺序被调用:

    1. static getDerivedStateFromProps()
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate()
    5. componentDidUpdate()

⬆ 返回顶部

  1. 错误处理期间会调用哪些方法?

    当渲染过程中、生命周期方法中或任何子组件的构造函数中发生错误时,会调用以下方法。

    1. static getDerivedStateFromError()
    2. componentDidCatch()

⬆ 返回顶部

  1. unmountComponentAtNode 方法的作用是什么?

    此方法来自 react-dom 包,它用于从 DOM 中移除已挂载的 React 组件,并清理其事件处理程序和状态。如果容器中没有挂载组件,调用此函数不会执行任何操作。如果组件被成功卸载,则返回 true;如果没有可卸载的组件,则返回 false。

    该方法的签名如下:

    ReactDOM.unmountComponentAtNode(container);
    

⬆ 返回顶部

  1. 高阶组件(HOC)有哪些局限性?

    高阶组件除了带来好处外,也存在一些需要注意的问题。以下是按顺序列出的几点:

    1. 不要在 render 方法中使用 HOC: 不建议在组件的 render 方法内部将 HOC 应用于组件。

      render() {
        // 每次 render 都会创建一个新的 EnhancedComponent 版本
        // EnhancedComponent1 !== EnhancedComponent2
        const EnhancedComponent = enhance(MyComponent);
        // 这会导致整个子树每次都卸载/重新挂载!
        return <EnhancedComponent />;
      }
      

      上述代码会因重新挂载组件而影响性能,导致该组件及其所有子组件的状态丢失。相反,应在组件定义外部应用 HOC,这样生成的组件只会创建一次。

    2. 静态方法必须手动复制: 当你将 HOC 应用于某个组件时,新组件不会拥有原始组件的任何静态方法。

      // 定义一个静态方法
      WrappedComponent.staticMethod = function () {
        /*...*/
      };
      // 现在应用 HOC
      const EnhancedComponent = enhance(WrappedComponent);
      
      // 增强后的组件没有该静态方法
      typeof EnhancedComponent.staticMethod === "undefined"; // true
      

      你可以通过在返回容器组件之前复制这些方法来解决此问题:

      function enhance(WrappedComponent) {
        class Enhance extends React.Component {
          /*...*/
        }
        // 必须明确知道要复制哪些方法 :(
        Enhance.staticMethod = WrappedComponent.staticMethod;
        return Enhance;
      }
      
    3. Refs 不会被传递: 对于 HOC,你需要将所有 props 传递给被包装的组件,但这不适用于 refs。这是因为 ref 并非真正意义上的 prop,它与 key 类似。在这种情况下,你需要使用 React.forwardRef API。

⬆ 返回顶部

  1. 如何在 DevTools 中调试 forwardRefs?

    React.forwardRef 接受一个渲染函数作为参数,DevTools 使用此函数来确定 ref 转发组件的显示名称。

    例如,如果你没有为渲染函数命名,也没有使用 displayName 属性,那么它在 DevTools 中会显示为 ”ForwardRef”:

    const WrappedComponent = React.forwardRef((props, ref) => {
      return <LogProps {...props} forwardedRef={ref} />;
    });
    

    但是,如果你为渲染函数命名,它将显示为 ”ForwardRef(myFunction)”

    const WrappedComponent = React.forwardRef(function myFunction(props, ref) {
      return <LogProps {...props} forwardedRef={ref} />;
    });
    

    作为替代方案,你也可以为 forwardRef 函数设置 displayName 属性:

    function logProps(Component) {
      class LogProps extends React.Component {
        // ...
      }
    
      function forwardRef(props, ref) {
        return <LogProps {...props} forwardedRef={ref} />;
      }
    
      // 为这个组件在 DevTools 中设置一个更有帮助的显示名称。
      // 例如:"ForwardRef(logProps(MyComponent))"
      const name = Component.displayName || Component.name;
      forwardRef.displayName = `logProps(${name})`;
    
      return React.forwardRef(forwardRef);
    }
    

⬆ 返回顶部

  1. 在 render 方法中使用箭头函数是否合适?

    是的,可以使用。这通常是向回调函数传递参数的最简单方法。但是在使用时需要注意性能优化。

    class Foo extends Component {
      handleClick() {
        console.log("Click happened");
      }
      render() {
        return <button onClick={() => this.handleClick()}>Click Me</button>;
      }
    }
    

    注意: 在 render 方法中使用箭头函数会在每次组件渲染时创建一个新函数,这可能会产生性能影响。

⬆ 返回顶部

  1. 如何理解状态更新是合并的?

    当你在组件中调用 setState() 时,React 会将你提供的对象合并到当前状态中。

    例如,假设我们有一个 Facebook 用户,其状态变量包含 posts 和 comments 详情:

      constructor(props) {
        super(props);
        this.state = {
          posts: [],
          comments: []
        };
      }
    

    现在,你可以通过单独的 setState() 调用来独立更新它们:

     componentDidMount() {
        fetchPosts().then(response => {
          this.setState({
            posts: response.posts
          });
        });
    
        fetchComments().then(response => {
          this.setState({
            comments: response.comments
          });
        });
      }
    

    如上面的代码片段所示,this.setState({comments}) 仅更新 comments 变量,而不会修改或替换 posts 变量。

⬆ 返回顶部

  1. 如何向事件处理程序传递参数?

    在迭代或循环过程中,向事件处理程序传递额外参数是很常见的需求。这可以通过箭头函数或 bind 方法来实现。

    让我们以网格中用户详情更新为例:

    <button onClick={(e) => this.updateUser(userId, e)}>更新用户详情</button>
    <button onClick={this.updateUser.bind(this, userId)}>更新用户详情</button>
    

    在这两种方法中,合成事件参数 e 都作为第二个参数传递。对于箭头函数,你需要显式传递它;而对于 bind 方法,它会自动传递。

⬆ 返回顶部

  1. 如何阻止组件渲染?

    你可以通过根据特定条件返回 null 来阻止组件渲染。这样可以实现组件的条件渲染。

    function Greeting(props) {
      if (!props.loggedIn) {
        return null;
      }
    
      return <div className="greeting">welcome, {props.name}</div>;
    }
    
    class User extends React.Component {
      constructor(props) {
        super(props);
        this.state = {loggedIn: false, name: 'John'};
      }
    
      render() {
       return (
           <div>
             // 如果未登录,则阻止组件渲染
             <Greeting loggedIn={this.state.loggedIn} />
             <UserDetails name={this.state.name}>
           </div>
       );
      }
    

    在上面的例子中,greeting 组件通过应用条件并返回 null 值,跳过了其渲染部分。

⬆ 返回顶部

  1. 举例说明如何使用 Context?

    Context 旨在共享对于 React 组件树而言可视为 全局 的数据。

    例如,在下面的代码中,我们手动传递一个 “theme” prop 来为 Button 组件设置样式。

    // 创建一个默认主题值为 "luna" 的 context
    const ThemeContext = React.createContext("luna");
    // 创建 App 组件,它使用 provider 在组件树中传递 theme 值
    class App extends React.Component {
      render() {
        return (
          <ThemeContext.Provider value="nova">
            <Toolbar />
          </ThemeContext.Provider>
        );
      }
    }
    // 中间组件,不再需要传递 theme prop
    function Toolbar(props) {
      return (
        <div>
          <ThemedButton />
        </div>
      );
    }
    // 在按钮组件中读取 theme 值并使用
    class ThemedButton extends React.Component {
      static contextType = ThemeContext;
      render() {
        return <Button theme={this.context} />;
      }
    }
    

⬆ 返回顶部

  1. 如何使用 contextType?

    ContextType 用于消费 context 对象。contextType 属性有两种使用方式:

    1. 作为类的属性: 类上的 contextType 属性可以被赋值为 React.createContext() 创建的 Context 对象。之后,你可以在任何生命周期方法和 render 函数中使用 this.context 来消费该 Context 类型最近的当前值。

      如下所示,为 MyClass 分配 contextType 属性:

      class MyClass extends React.Component {
        componentDidMount() {
          let value = this.context;
          /* 在挂载时使用 MyContext 的值执行副作用 */
        }
        componentDidUpdate() {
          let value = this.context;
          /* ... */
        }
        componentWillUnmount() {
          let value = this.context;
          /* ... */
        }
        render() {
          let value = this.context;
          /* 根据 MyContext 的值渲染内容 */
        }
      }
      MyClass.contextType = MyContext;
      
    2. 静态字段: 你可以使用静态类字段语法来初始化 contextType。

      class MyClass extends React.Component {
        static contextType = MyContext;
        render() {
          let value = this.context;
          /* 根据该值渲染内容 */
        }
      }
      

⬆ 返回顶部

  1. 什么是 Consumer?

    Consumer 是一个订阅 context 变化的 React 组件。它要求一个函数作为子元素,该函数接收当前的 context 值作为参数并返回一个 React 节点。传递给函数的 value 参数将等于树中上方最近的该 context 的 Provider 的 value prop。

    举一个简单的例子:

    <MyContext.Consumer>
      {value => /* 根据 context 值渲染内容 */}
    </MyContext.Consumer>
    

⬆ 返回顶部

  1. 使用 Context 时如何解决性能方面的边缘情况?

    Context 使用引用标识来确定何时重新渲染,因此存在一些可能在 Provider 的父组件重新渲染时触发消费者意外渲染的陷阱。

    例如,下面的代码每次 Provider 重新渲染时都会重新渲染所有消费者,因为 value 始终会创建一个新对象。

    class App extends React.Component {
      render() {
        return (
          <Provider value={{ something: "something" }}>
            <Toolbar />
          </Provider>
        );
      }
    }
    

    这可以通过将 value 提升到父组件的 state 中来解决:

    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          value: { something: "something" },
        };
      }
    
      render() {
        return (
          <Provider value={this.state.value}>
            <Toolbar />
          </Provider>
        );
      }
    }
    

⬆ 返回顶部

  1. 在 HOC 中 forward ref 的目的是什么?

    Refs 不会被自动传递,因为 ref 不是一个 prop。React 对其的处理方式与 key 类似。如果你向 HOC 添加一个 ref,该 ref 将引用最外层的容器组件,而不是被包装的组件。在这种情况下,你可以使用 Forward Ref API。例如,我们可以使用 React.forwardRef API 将 refs 显式转发到内部的 FancyButton 组件。

    下面的 HOC 会记录所有 props:

    function logProps(Component) {
      class LogProps extends React.Component {
        componentDidUpdate(prevProps) {
          console.log("旧 props:", prevProps);
          console.log("新 props:", this.props);
        }
    
        render() {
          const { forwardedRef, ...rest } = this.props;
    
          // 将自定义 prop "forwardedRef" 作为 ref 赋值
          return <Component ref={forwardedRef} {...rest} />;
        }
      }
    
      return React.forwardRef((props, ref) => {
        return <LogProps {...props} forwardedRef={ref} />;
      });
    }
    

    让我们使用这个 HOC 来记录传递给我们的 “fancy button” 组件的所有 props:

    class FancyButton extends React.Component {
      focus() {
        // ...
      }
    
      // ...
    }
    export default logProps(FancyButton);
    

    现在,让我们创建一个 ref 并将其传递给 FancyButton 组件。在这种情况下,你可以设置按钮元素的焦点。

    import FancyButton from "./FancyButton";
    
    const ref = React.createRef();
    ref.current.focus();
    <FancyButton label="Click Me" handleClick={handleClick} ref={ref} />;
    

⬆ 返回顶部

  1. ref 参数是否对所有函数或类组件都可用?

    常规函数或类组件不会接收 ref 参数,ref 也不在 props 中可用。第二个 ref 参数仅在你使用 React.forwardRef 调用定义组件时存在。

⬆ 返回顶部

  1. 为什么在组件库中使用 forward refs 需要额外注意?

    当你开始在组件库中使用 forwardRef 时,应将其视为一个破坏性变更,并发布库的新版本。这是因为你的库可能会有不同的行为,例如 refs 被分配到何处以及导出的类型是什么。这些变化可能会破坏依赖旧行为的应用程序和其他库。

⬆ 返回顶部

  1. 如何不使用 ES6 创建 React 类组件?

    如果你不使用 ES6,那么你可能需要使用 create-react-class 模块。对于默认 props,你需要在传递的对象上定义 getDefaultProps() 作为一个函数。而对于初始状态,你必须提供一个单独的 getInitialState 方法,该方法返回初始状态。

    var Greeting = createReactClass({
      getDefaultProps: function () {
        return {
          name: "Jhohn",
        };
      },
      getInitialState: function () {
        return { message: this.props.message };
      },
      handleClick: function () {
        console.log(this.state.message);
      },
      render: function () {
        return <h1>Hello, {this.props.name}</h1>;
      },
    });
    

    注意: 如果你使用 createReactClass,那么所有方法都支持自动绑定。也就是说,你不需要在构造函数中为事件处理程序使用 .bind(this)

⬆ 返回顶部

  1. 不使用 JSX 能否使用 React?

    可以,JSX 并非使用 React 的必需品。当你不想在构建环境中设置编译时,不使用 JSX 反而很方便。每个 JSX 元素实际上只是调用 React.createElement(component, props, ...children) 的语法糖。

    例如,让我们看一个使用 JSX 的问候示例:

    class Greeting extends React.Component {
      render() {
        return <div>Hello {this.props.message}</div>;
      }
    }
    
    ReactDOM.render(
      <Greeting message="World" />,
      document.getElementById("root")
    );
    

    你可以将相同的代码写成不使用 JSX 的形式:

    class Greeting extends React.Component {
      render() {
        return React.createElement("div", null, `Hello ${this.props.message}`);
      }
    }
    
    ReactDOM.render(
      React.createElement(Greeting, { message: "World" }, null),
      document.getElementById("root")
    );
    

⬆ 返回顶部

  1. 如何使用 render props 创建 HOC?

    你可以使用带有 render prop 的常规组件来实现大多数高阶组件(HOC)。例如,如果你希望有一个 withMouse HOC 而不是一个 组件,你可以很容易地使用一个带有 render prop 的常规 组件来创建它。

    function withMouse(Component) {
      return class extends React.Component {
        render() {
          return (
            <Mouse
              render={(mouse) => <Component {...this.props} mouse={mouse} />}
            />
          );
        }
      };
    }
    

    这样,render props 提供了使用这两种模式的灵活性。

⬆ 返回顶部

  1. 什么是 react scripts?

    react-scripts 包是 create-react-app 启动工具包中的一组脚本,它帮助你无需配置即可快速开始项目。react-scripts start 命令会设置开发环境并启动服务器,同时支持热模块替换。

⬆ 返回顶部

  1. create react app 有哪些特性?

    以下是 create react app 提供的一些特性列表。

    1. 支持 React、JSX、ES6、Typescript 和 Flow 语法。
    2. 自动添加 CSS 前缀
    3. CSS 重置/标准化(CSS Reset/Normalize)
    4. 实时开发服务器
    5. 快速交互式单元测试运行器,内置覆盖率报告支持
    6. 用于打包生产环境的 JS、CSS 和图像的构建脚本,带有哈希和 sourcemap
    7. 离线优先的 service worker 和 web app 清单,满足所有渐进式 Web 应用(PWA)标准。

⬆ 返回顶部

  1. renderToNodeStream 方法的作用是什么?

    ReactDOMServer#renderToNodeStream 方法用于在服务器端生成 HTML,并在初始请求时将标记发送到客户端,以加快页面加载速度。它还有助于搜索引擎轻松爬取你的页面,有利于 SEO。 注意: 请记住,此方法仅在服务器端可用,在浏览器中不可用。

⬆ 返回顶部

  1. 如何使用 create-react-app 获取 Redux 脚手架?

    Redux 团队为 create-react-app 项目提供了官方的 redux+js 或 redux+typescript 模板。生成的项目设置包括:
    1. Redux Toolkit 和 React-Redux 依赖
    2. 创建和配置 Redux store
    3. React-Redux <Provider> 将 store 传递给 React 组件
    4. 小型的 "计数器" 示例,演示如何添加 redux 逻辑以及如何使用 React-Redux hooks API 从组件与 store 交互 需要执行以下命令,并附带 template 选项:
    5. Javascript 模板:
    npx create-react-app my-app --template redux
    
    1. Typescript 模板:
    npx create-react-app my-app --template redux-typescript
    

⬆ 返回顶部

  1. 什么是状态突变(state mutation)以及如何防止它?

    状态突变 指的是在未使用 setState 函数的情况下尝试更新组件状态的行为。当你使用状态变量进行某些计算,并在不知不觉中将结果保存回同一个状态变量时,就可能发生这种情况。这也是为什么建议在 reducer 中通过使用 Object.assign({}, ...) 或扩展语法(spread syntax)返回状态变量的新实例的主要原因。

    这可能会导致 UI 出现未知问题,因为状态变量的值在未通知 React 检查哪些组件会受此更新影响的情况下就被修改了,进而可能引发 UI 错误。

    示例:

    class A extends React.component {
      constructor(props) {
        super(props);
        this.state = {
          loading: false
        }
     }
    
    componentDidMount() {
      let { loading } = this.state;
      loading = (() => true)(); // 尝试执行某个操作并直接保存到状态变量中
    }
    
    

    如何防止: 确保你的状态变量是不可变的,可以通过使用像 Immutable.js 这样的插件来强制实现不可变性,始终使用 setState 进行更新,以及在 reducer 中返回新的实例来传递更新后的状态值。

⬆ 返回顶部

  1. 什么是 React Mixins?

    ⚠️ 已弃用: Mixins 被视为遗留技术,不应在现代 React 应用中使用。

    Mixins 是一种在使用 React.createClass() 时在组件之间共享通用功能的方式。然而,它们带来了几个问题:

    • 隐式依赖
    • 命名冲突
    • 复杂性滚雪球式增长

    最常用的 mixins 之一是 PureRenderMixin

    const PureRenderMixin = require("react-addons-pure-render-mixin");
    
    const Button = React.createClass({
      mixins: [PureRenderMixin],
      // ...
    });
    

    现代替代方案:

    • React.memo() 用于函数组件(替代 PureRenderMixin)
    • 自定义 Hooks 用于共享有状态逻辑
    • 高阶组件(HOCs) 用于处理横切关注点
    • Render Props 模式
    // 使用 React.memo 的现代等效写法
    const Button = React.memo(({ onClick, label }) => (
      <button onClick={onClick}>{label}</button>
    ));
    

    ⬆ 返回顶部

  2. 在 React 16.9 中可以使用 javascript urls 吗?

    ⚠️ 已弃用: 从 React 19 开始,javascript: URLs 不再受支持,并可能抛出错误。

    在 React 16.9 中,javascript: URLs 会在控制台中记录一个警告,因为以 javascript: 开头的 URL 存在危险的安全漏洞(XSS 攻击)。

    const companyProfile = {
      website: "javascript: alert('Your website is hacked')",
    };
    // 这在 React 16.9 中会记录一个警告
    // 现在在现代 React 中会被阻止或抛出错误
    <a href={companyProfile.website}>More details</a>;
    

    现代最佳实践:

    • 永远不要使用 javascript: URLs
    • 改用适当的事件处理程序:
    const handleClick = (e) => {
      e.preventDefault();
      // 你的逻辑在这里
    };
    
    <a href="#" onClick={handleClick}>More details</a>
    // 或者更好的方式:
    <button onClick={handleClick}>More details</button>
    

⬆ 返回顶部

免责声明

本仓库中的问题是多家公司常见面试问题的汇总。我们无法保证这些问题一定会在您的面试过程中被问到,您也不应专注于死记硬背所有问题。主要目的是让您了解部分公司可能会问到的问题类型——如果您不能回答所有问题,也不要灰心——这很正常!

祝您面试顺利 😊


Introduction

《ReactJS面试问题及答案精选500条……编程练习题目即将上线!!》【此简介由AI生成】

Customize your domain
63244.8 K10.39 KVisit GitHub