一、react基础面试题
1.react中keys的作用是什么?
key是是用于追踪哪些列表被修改,被添加或者被移除的辅助标识。
在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。
2.react中refs的作用是什么?
class组件中:
Refs 是 React 提供给我们的安全访问 DOM 元素或者某个组件实例的句柄。我们可以为元素添加 ref 属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回:
class CustomForm extends Component {
handleSubmit = () => {
console.log("Input Value: ", this.input.value)
}
render () {
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
ref={(input) => this.input = input} />
<button type='submit'>Submit</button>
</form>
)
}
}
function组件中:
函数式组件同样能够利用闭包暂存其值:
function CustomForm ({handleSubmit}) {
let inputElement = useRef({})
return (
<form onSubmit={() => handleSubmit(inputElement.current.value)}>
<input
type='text'
ref={inputElement} />
<button type='submit'>Submit</button>
</form>
)
}
3.react中三种构建组件的方式?
1)React.createClass():很少使用
2)ES6 class:class组件
3)无状态函数:多指的是函数组件
4.调用setstate之后发生了什么?
1)状态合并:
在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。
2)构建渲染react树:
经过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树并且着手重新渲染整个 UI 界面。
3)diff节点差异:
在 React 得到元素树之后,React 会自动计算出新的树与老树的节点差异,然后根据差异对界面进行最小化重渲染。
4)按需更新:
在差异计算算法中,React 能够相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是全部重新渲染。
5.react diff原理
1)把树形结构按照层级分解,只比较同级元素
2)列表添加唯一key
3)只匹配相同class的component(这里面的class指的是组件的名字)
4)合并,调用setstate方法的时候,react将其标记为dirty,到每一个事件循环结束,react检查标记dirty的component重新绘制。
5)选择性子树渲染,开发人员可以重写shouldComponentUpdate提高diff的性能(function组件useMemo加memo提高性能)
react diff和vue diff的区别?
1)Diff 算法的基本原理
React
- 单节点比较:React 的 diff 算法主要关注同一层级的节点比较,即只会比较同一层级的节点,不会跨层级比较。
- 同层级比较:React 通过 key 属性来标识每个节点,确保在比较时能够快速找到对应的节点。如果没有 key 属性,React 会按顺序比较节点,这可能导致不必要的重新渲染。
- 递归比较:React 会对每个节点及其子节点进行递归比较,确保整个树的差异都被捕获。
Vue
- 多节点比较:Vue 的 diff 算法不仅比较同一层级的节点,还会考虑节点的移动和插入操作。
- 基于索引的比较:Vue 使用索引来跟踪节点的位置,通过索引优化节点的移动和插入操作,减少不必要的 DOM 操作。
- 双端比较:Vue 采用双端比较策略,从两端同时开始比较,减少中间部分的比较次数,提高效率。
2)Key 属性的使用
React
- 关键作用:在 React 中,key 属性是 diff 算法的关键。它用于唯一标识每个节点,确保在状态变化时能够快速找到对应的节点。
- 性能影响:如果 key 属性使用不当,会导致不必要的重新渲染。因此,建议使用唯一且稳定的 key 值。
Vue
- 可选但推荐:在 Vue 中,key 属性也是推荐使用的,但不是必须的。Vue 会尝试通过索引和节点类型来优化 diff 算法,但如果使用了 key 属性,可以进一步提高性能。
3)渲染优化
React
- 批量更新:React 会批量处理多个状态更新,减少不必要的渲染。这通过
React.batchedUpdates
或者在事件处理和生命周期方法中自动实现。- 懒加载:React 16 引入了 Fiber 架构,支持异步渲染和优先级调度,进一步优化了渲染性能。
Vue
- 异步更新:Vue 也采用了异步更新策略,将多个状态更新合并成一次 DOM 更新,减少不必要的渲染。
- 细粒度的更新:Vue 会尽量减少 DOM 操作的次数,通过细粒度的更新策略,只更新必要的部分。
4)性能对比
React
- 高性能:React 的 Fiber 架构和批量更新机制使得它在处理复杂应用和大量数据时表现出色。
- 灵活性:React 的 diff 算法相对灵活,适用于各种复杂场景。
Vue
- 高效且简洁:Vue 的双端比较策略和基于索引的优化使得它在处理简单和中等复杂度的应用时表现优异。
- 易用性:Vue 的 API 设计更加简洁,上手更容易,适合快速开发。
5)实际应用
React
- 适用场景:React 适合大型、复杂的应用,特别是那些需要高度定制和灵活状态管理的项目。
- 生态系统:React 拥有丰富的生态系统和社区支持,提供了大量的库和工具。
Vue
- 适用场景:Vue 适合中小型项目,特别是那些需要快速开发和简洁 API 的项目。
- 生态系统:Vue 也有成熟的生态系统,提供了许多有用的插件和工具。
总结
- React 的 diff 算法主要关注同一层级的节点比较,通过 key 属性优化性能,支持批量更新和异步渲染。
- Vue 的 diff 算法采用双端比较策略,通过索引优化节点的移动和插入操作,支持异步更新和细粒度的更新策略。
6.为什么传递给setState的参数是一个callback而不是一个对象?
因为 this.props 和 this.state 的更新可能是异步的,不能依赖它们的值去计算下一个 state。
7.除了在构造函数中绑定this,还有其他方式吗?
你可以使用属性初始值设定项(property initializers)来正确绑定回调,create-react-app 也是默认支持的。在回调中你可以使用箭头函数,但问题是每次组件渲染时都会创建一个新的回调。
8.setState第二个参数的作用
因为setState是一个异步的过程,所以说执行完setState之后不能立刻更改state里面的值。如果需要对state数据更改监听,setState提供第二个参数,就是用来监听state里面数据的更改,当数据更改完成,调用回调函数
9.(在构造函数中)调用super(props)的目的是什么?
1)在 super() 被调用之前,子类是不能使用 this 的。
2)子类必须在 constructor 中调用 super()。
3)传递 props 给 super() 的原因则是便于(在子类中)能在 constructor 访问 this.props。
10.简述flux思想
最大特点,就是数据的"单向流动"。
1)用户访问connect封装的组件 View
2)View 发出用户的 Action
3)Dispatcher 收到 Action,要求 Store 调用reducer对state进行相应的更新
4)Store 更新后,发出一个"change"事件
5)View 收到"change"事件后,根据更新的state刷新页面
11.在React中Element和Component的区别?
1)一个 React element 描述了你想在屏幕上看到什么。
换个说法就是,一个 React element 是一些 UI 的对象表示。
2)一个 React Component 是一个函数或一个类,它可以接受输入并返回一个 React element t(通常是通过 JSX ,它被转化成一个 createElement 调用)。
12.描述事件在React中的处理方式?
1)为了解决跨浏览器兼容性问题,您的 React 中的事件处理程序将传递 SyntheticEvent 的实例,它是 React 的浏览器本机事件的跨浏览器包装器。
2)这些 SyntheticEvent 与您习惯的原生事件具有相同的接口,除了它们在所有浏览器中都兼容。
3)有趣的是,React 实际上并没有将事件附加到子节点本身。React 将使用单个事件监听器监听顶层的所有事件。这对于性能是有好处的,这也意味着在更新 DOM 时,React 不需要担心跟踪事件监听器。
13.createElement和cloneElement有什么区别?
createElement:
1)React.createElement():JSX 语法就是用 React.createElement()来构建 React 元素的。
2)它接受三个参数,第一个参数可以是一个标签名。如 div、span,或者 React 组件。第二个参数为传入的属性。第三个以及之后的参数,皆作为组件的子组件。
React.createElement(
type,
[props],
[...children]
)
cloneElement:
1)React.cloneElement()与 React.createElement()相似。
2)不同的是它传入的第一个参数是一个 React 元素,而不是标签名或组件。
3)新添加的属性会并入原有的属性,传入到返回的新元素中,而就的子元素奖杯替换。
React.cloneElement(
element,
[props],
[...children]
)
14.如何告诉react它应该编译产生环境版本?
1)通常情况下我们会使用 Webpack 的 DefinePlugin 方法来将 NODE_ENV 变量值设置为 production。
2)编译版本中 React 会忽略 propType 验证以及其他的告警信息,同时还会降低代码库的大小,React 使用了 Uglify 插件来移除生产环境下不必要的注释等信息。
15.Controlled Component和Uncontrolled Component之间的区别是什么?
1)受控组件(Controlled Component):
代指那些交由 React 控制并且所有的数据统一存放的组件。
2)非受控组件(Uncontrolled Component):
则是由DOM存放表单数据,并非存放在 React 组件中。我们可以使用 refs 来操控DOM元素
注意:
不过实际开发中我们并不提倡使用非受控组件,因为实际情况下我们需要更多的考虑表单验证、选择性的开启或者关闭按钮点击、强制输入格式等功能支持,而此时我们将数据托管到 React 中有助于我们更好地以声明式的方式完成这些功能。引入 React 或者其他 MVVM 框架最初的原因就是为了将我们从繁重的直接操作 DOM 中解放出来。
16.白屏问题及优化方案
vue,react白屏问题及解决方案
1)js问题
- 单页面的应用html也是依靠JS生成,在渲染页面的时候需要加载很大的JS文件( app.js 和vendor.js ),在JS解析加载完成之前无法展示页面,从而导致了白屏
- 当网速不佳的时候也会产生一定程度的白屏。
2)浏览器兼容问题
3)url地址无效或者含有中文字符
4)缓存导致
前端项目打包后,在非首次线上替换dist文件时,某些手机/浏览器在之后首次打开页面,可能出现白屏情况
原因:
- 在用户端会默认缓存index.html入口文件,而由于vue打包生成的css/js都是哈希值,跟上次的文件名都不同,因此会出现找不到css/js的情况,导致白屏的产生。
- 在服务端更新包之后,由于旧的文件被删除,而index.html所链接的路径依然是旧文件路径,因此会找不到文件,从而白屏。
5)页面报错
解决办法:
思路:减小打包后的体积(sourceMap关掉,CDN引入, 路由懒加载,组件按需加载)
提高渲染速度;
优化用户体验;
预加载:
在 Webpack 4 中,配置预加载(Preloading)和预取(Prefetching)可以帮助浏览器更高效地加载资源,从而提升用户体验。预加载会在当前页面加载时异步下载资源,而预取则会在浏览器空闲时下载资源,以便在用户导航到下一个页面时更快地加载。
配置预加载(Preloading)
预加载可以通过在
output
配置中设置publicPath
和filename
,并在optimization.runtimeChunk
和optimization.splitChunks
中进行配置来实现。示例:
const path = require('path'); module.exports = { entry: { main: './src/index.js' }, output: { filename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist'), publicPath: '/' }, optimization: { runtimeChunk: 'single', splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html' }) ] };
使用
link
标签进行预加载你可以在 HTML 文件中手动添加
link
标签来预加载资源。示例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My App</title> <link rel="preload" href="/main.[contenthash].js" as="script"> <link rel="preload" href="/vendors.[contenthash].js" as="script"> </head> <body> <div id="app"></div> <script src="/main.[contenthash].js"></script> <script src="/vendors.[contenthash].js"></script> </body> </html>
使用
HtmlWebpackPlugin
自动生成link
标签
HtmlWebpackPlugin
可以自动生成 HTML 文件,并且支持预加载和预取。安装
HtmlWebpackPlugin
npm install --save-dev html-webpack-plugin
配置
HtmlWebpackPlugin
const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: { main: './src/index.js' }, output: { filename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist'), publicPath: '/' }, optimization: { runtimeChunk: 'single', splitChunks: { cacheGroups: { vendor: { test: /[\\/]node_modules[\\/]/, name: 'vendors', chunks: 'all' } } } }, plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', preload: true, // 启用预加载 prefetch: true // 启用预取 }) ] };
使用
webpack
的magic-comments
进行预加载和预取你可以在
import
语句中使用magic-comments
来指定预加载和预取。示例
import(/* webpackMode: "lazy", webpackPrefetch: true */ './componentA'); import(/* webpackMode: "lazy", webpackPreload: true */ './componentB');
总结
- 预加载:使用
link
标签或HtmlWebpackPlugin
自动生成link
标签。- 预取:使用
HtmlWebpackPlugin
或magic-comments
。- 配置
HtmlWebpackPlugin
:启用preload
和prefetch
选项。- 使用
magic-comments
:在import
语句中指定预加载和预取模式。通过这些方法,你可以有效地配置预加载和预取,提升应用的加载性能。
CDN资源优化:
- 将依赖的第三方npm包全部改为通过CDN链接获取,在index.html里插入相应链接
- 在webpack里配置externals属性
- 卸载相关依赖的npm包
使用gzip压缩
nginx开启gzip压缩
gzip on; gzip_static on; //当存在.gzip格式的js文件时,优先使用静态文件 gzip_min_length 10k; //开启gzip压缩的最小大小 gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 6; gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml; gzip_vary on; gzip_proxied expired no-cache no-store private auth; gzip_disable "MSIE [1-6]\.";
SSR服务器渲染
首页加loading或骨架屏(仅仅是在体验上优化)
elementU有骨架屏组件(Skeleton)
所谓的骨架屏,就是在页面内容未加载完成的时候,先使用一些图形进行占位,待内容加载完成之后再把它替换掉。在这个过程中用户会感知到内容正在逐渐加载并即将呈现,降低了“白屏”的不良体验。
总结:
- 在HTML内实现Loading状态或者骨架屏
- 去掉外联 css
- 缓存基础框架
- 使用动态 polyfill
注意:在你的React项目中按需导入所需的Polyfill,入口文件(通常是
src/index.js
)中添加
- 使用 SplitChunksPlugin 拆分公共代码
- 压缩css
- 预加载
- 正确地使用 Webpack 4.0 的 Tree Shaking
const path = require('path'); const webpack = require('webpack'); module.exports = { // 设置入口文件 entry: './src/index.js', // 设置输出配置 output: { filename: 'bundle.js', path: path.resolve(__dirname, 'dist'), // 设置库的类型,为了支持 Tree Shaking libraryTarget: 'umd' }, // 设置模块解析规则 module: { rules: [ { test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env', '@babel/preset-react'] } } } ] }, // 设置插件 plugins: [ // 用于优化模块ID,减少文件大小 new webpack.optimize.ModuleConcatenationPlugin() ], // 启用性能提示 performance: { hints: 'warning', // 对性能问题给出警告 maxEntrypointSize: 512000, // 500 kB maxAssetSize: 512000, // 500 kB assetFilter: function(assetFilename) { // 过滤掉源码映射文件 return assetFilename.endsWith('.js'); } }, // 配置 mode 为 'production' 以启用优化 mode: 'production' };
- 使用动态 import,切分页面代码,减小首屏 JS 体积
- 编译到 ES2015+,提高代码运行效率,减小体积
- 使用 lazyload 和 placeholder 提升加载体验
import React, { lazy, Suspense } from 'react'; const LazyComponent = lazy(() => import('./LazyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <LazyComponent /> </Suspense> ); }
二、React组件面试题
1.展示组件和容器组件有何不同?
1.1展示组件:
关心组件看起来是什么。展示专门通过 props 接受数据和回调,并且几乎不会有自身的状态,但当展示组件拥有自身的状态时,通常也只关心 UI 状态而不是数据的状态。
1.2容器组件:1)更关心组件是如何运作的。
2)容器组件会为展示组件或者其它容器组件提供数据和行为(behavior),它们会调用 Flux actions,并将其作为回调提供给展示组件。
3)容器组件经常是有状态的,因为它们是(其它组件的)数据源。
2.类组件和函数组件有何不同?
1)类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问 store 并维持状态
2)当组件仅是接收 props,并将组件自身渲染到页面时,该组件就是一个 '无状态组件(stateless component)',可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件
3.(组件的)状态(state)和属性(props)之间有何不同?
1)State :
是一种数据结构,用于组件挂载时所需数据的默认值。State 可能会随着时间的推移而发生突变,但多数时候是作为用户事件行为的结果。
2)Props(properties 的简写):则是组件的配置。props 由父组件传递给子组件,并且就子组件而言,props 是不可变的(immutable)。组件不能改变自身的 props,但是可以把其子组件的 props 放在一起(统一管理)。Props 也不仅仅是数据--回调函数也可以通过 props 传递。
4.何为受控组件?
[state,setState]=useState(),比如输入框,它的值通过 React 的这种方式来控制,这样的元素就被称为"受控元素"。
5.何为高阶组件?
1)高阶组件是一个以组件为参数并返回一个新组件的函数。
2)HOC 运行你重用代码、逻辑和引导抽象。
3)最常见的可能是 Redux 的 connect 函数。
4)除了简单分享工具库和简单的组合,HOC 最好的方式是共享 React 组件之间的行为。
如果你发现你在不同的地方写了大量代码来做同一件事时,就应该考虑将代码重构为可重用的 HOC。
6.应该在React组件的何处发起ajax请求?
1)在 React 组件中,应该在 componentDidMount 中发起网络请求。
2)这个方法会在组件第一次“挂载”(被添加到 DOM)时执行,在组件的生命周期中仅会执行一次。
7.React中组件传值?
1)父传子(组件嵌套浅):父组件定义一个属性,子组件通过this.props接收。
2)子传父:父组件定义一个属性,并将一个回调函数赋值给定义的属性,然后子组件进行调用传过来的函数,并将参数传进去,在父组件的回调函数中即可获得子组件传过来的值。3)父获取子组件的数据和方法:父组件传递ref给Child组件;forwardRef封装子组件,使用useImperativeHandle钩子保留数据和方法给父组件使用:
import React, { forwardRef, useImperativeHandle } from 'react'; // 子组件 const ChildComponent = forwardRef((props, ref) => { // 使用 useImperativeHandle 自定义公开的方法 useImperativeHandle(ref, () => ({ getValue: () => props.value })); return <input type="text" {...props} />; }); // 父组件 const ParentComponent = () => { const childRef = useRef(null); const handleClick = () => { if (childRef.current) { alert(childRef.current.getValue()); } }; return ( <> <ChildComponent ref={childRef} value="我是Child组件的值" /> <button onClick={handleClick}>获取Child值</button> </> ); };
8.什么时候在功能组件上使用类组件?
如果您的组件具有状态( state )或生命周期方法,请使用 Class 组件。否则,使用功能组件
9.受控组件和非受控组件有什么区别?
受控组件:
1)数据驱动,使用state状态渲染组件
非受控组件:
1)不受控制( uncontrolled component )的组件是您的表单数据由 DOM 处理,而不是您的 React 组件。我们使用 refs 来完成这个。
10.React组件的划分业务组件技术组件?
根据组件的职责通常把组件分为 UI 组件和容器组件。
1)UI 组件负责 UI 的呈现;2)容器组件负责管理数据和逻辑。
3)两者通过 React-Redux 提供 connect 方法联系起来。
三、redux面试题
1.redux中间件
1)中间件提供第三方插件的模式,自定义拦截action->reducer的过程。
2)变为action->middlewares->reducer。
3)这种机制可以让我们改变数据流,实现异步action,action过滤,日志输出,异常报告等功能。
常见的中间件:
redux-logger:提供日志输出
redux-thunk:处理异步操作
redux-promise:处理异步操作,actionCreator的返回值是Promise
2.redux有什么缺点?
1)一个组件所需要的数据,必须由父组件传过来,而不能像redux中直接从store取。
2)当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。
3.说一下你理解的redux?
概念:
redux 是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理。
主要有三个核心方法,action,store,reducer。
redux的工作流程:
工作流程是 view 调用 store 的 dispatch 接收 action 传入 store,reducer 进行 state 操作,view 通过 store 提供的 getState 获取最新的数据。
flux的工作流程:
flux 也是用来进行数据操作的,有四个组成部分 action,dispatch,view,store,工作流程是 view 发出一个 action,派发器接收 action,让 store 进行数据更新,更新完成以后 store 发出 change,view 接受 change 更新视图。
redux和flux的区别:
1)主要区别在于 Flux 有多个可以改变应用状态的 store,在 Flux 中 dispatcher 被用来传递数据到注册的回调事件,但是在 redux 中只能定义一个可更新状态的 store,redux 把 store 和 Dispatcher 合并,结构更加简单清晰。
2)新增 state,对状态的管理更加明确,通过 redux,流程更加规范了,减少手动编码量,提高了编码效率,同时缺点时当数据更新时有时候组件不需要,但是也要重新绘制,有些影响效率。一般情况下,我们在构建多交互,多数据流的复杂项目应用时才会使用它们
4.redux和context的区别?
1) 设计理念
Redux:
- 全局状态管理: 集中式状态管理库,适用于大型复杂项目
- 单一数据源: 所有的状态都存储在一个单一的store中,确保状态的一致性和可预测性
- 不可变性: 状态是不可变的,任何状态的改变都需要通过纯函数(reducer)来处理
- 中间件支持: 支持中间件(如redux-thunk,redux-saga)来处理异步操作和其他复杂的逻辑
Context API:
- 局部状态管理: 主要用于在组件树中传递状态,适用于较小范围的状态管理
- 分散的数据源: 每个Context实例可以有自己的状态,适合管理特定的模块或功能的状态
- 简单易用: 不需要额外的库,直接使用React提供的API即可
2) 使用场景
Redux:
大型应用: 适用于需要管理大量全局状态的大型应用。
复杂状态逻辑: 适用于需要处理复杂状态逻辑和异步操作的应用。
团队协作: 适用于多人协作的项目,因为 Redux 的结构化设计使得状态管理和调试更加容易。
Context API:
小型应用: 适用于小型应用或简单的状态管理需求。
局部状态: 适用于需要在组件树中传递少量状态的情况。
- 快速开发: 适用于快速开发和原型设计,因为 Context API 的使用相对简单。
3) 实现方式:
Redux:
- store: 所有的状态都存储在一个单一的store中
- actions: 通过派发actions来触发状态的改变
- reducers: 纯函数,根据actions更新状态
- connect: 使用connec高阶组件或者useSelector和useDispatch钩子来连接组件和store
Context API:
- Context: 使用React.createContext创建一个上下文对象
- Provider: 使用Context.Provider组件提供状态
- Consumer: 使用Context.Consumer组件或useContext钩子来消费状态
4) 性能:
Redux:
- 性能开销: 由于所有的状态都集中在一个 store 中,可能会导致不必要的重新渲染。可以通过
reselect
等库来优化选择器,减少性能开销。- 调试工具: 提供了强大的调试工具,如
redux-devtools
,帮助开发者追踪状态变化。Context API:
- 性能较好: 由于状态是局部的,只有依赖该状态的组件才会重新渲染,性能相对较好。
- 简单调试: 由于结构简单,调试也相对容易。
5) 学习曲线
Redux:
- 学习曲线较陡: 需要理解概念如 store、actions、reducers、中间件等,适合有一定经验的开发者。
Context API:
- 学习曲线平缓: 使用简单,容易上手,适合初学者和小型项目。
总结
- Redux 适用于大型应用和复杂状态管理,提供了强大的工具和结构化的管理方式。
5.mobx和redux的区别?
设计理念:
MobX
- 响应式编程:MobX 基于响应式编程的思想,通过
observable
、computed
和action
等概念,使得状态的变化能够自动触发视图的更新。- 自动追踪依赖:MobX 自动追踪状态的变化,并且只在必要的时候更新相关的视图,减少了手动管理状态的复杂性。
Redux
- 单向数据流:Redux 基于 Flux 架构,采用单向数据流的设计模式。所有的状态变更都通过纯函数(reducer)来处理,确保状态的可预测性和可调试性。
- 集中管理状态:Redux 将所有的状态集中在一个全局的 store 中,通过 action 和 reducer 来管理状态的变化。
使用方式:
MobX
- 定义可观察状态:使用
observable
装饰器或makeAutoObservable
方法来定义可观察的状态。- 定义计算属性:使用
computed
装饰器或方法来定义计算属性,这些属性会根据可观察状态的变化自动更新。- 定义动作:使用
action
装饰器或方法来定义改变状态的动作。- 观察状态变化:使用
observer
高阶组件或useObserver
钩子来观察状态的变化,并自动更新视图。Redux
- 定义 Action:定义描述状态变化的动作对象(actions),通常包含一个类型(type)和一些载荷(payload)。
- 定义 Reducer:定义纯函数(reducers),根据接收到的 action 来更新状态。
- 创建 Store:使用
createStore
方法创建一个全局的 store,用于存储应用的状态。- 分发 Action:通过
dispatch
方法分发 action,触发状态的更新。- 连接组件:使用
connect
高阶组件或useSelector
和useDispatch
钩子将组件与 store 连接起来,获取状态和分发 action。学习曲线:
MobX
- 学习曲线较平缓:MobX 的 API 较为简单,上手容易,适合中小型项目快速开发。
- 灵活性高:MobX 提供了更多的灵活性,可以更自由地管理状态,适合复杂的业务逻辑。
Redux
- 学习曲线较陡峭:Redux 的概念较多,需要理解 action、reducer、store 等多个概念,上手相对较难。
- 结构清晰:Redux 的单向数据流设计使得状态管理更加规范和可预测,适合大型项目和团队协作。
性能:
MobX
- 性能优化:MobX 通过细粒度的依赖追踪,只在必要时更新视图,性能较好。
- 动态更新:由于 MobX 的响应式特性,状态的变化可以立即反映到视图上,减少了不必要的渲染。
Redux
- 批量更新:Redux 通过中间件(如
redux-batch
)可以实现批量更新,减少不必要的渲染。- 调试工具:Redux 提供了强大的调试工具(如 Redux DevTools),方便调试和回溯状态变化。
生态系统:
MobX
- 生态系统较小:虽然 MobX 也有一定的社区支持,但相对于 Redux,其生态系统较小,可用的中间件和插件较少。
Redux
- 生态系统丰富:Redux 拥有丰富的生态系统,提供了大量的中间件(如
redux-thunk
、redux-saga
)、插件和工具,适合复杂的应用场景。总结
- MobX 更适合需要快速开发、状态管理较为简单的项目,特别是那些对性能有较高要求的应用。
- Redux 更适合大型项目和团队协作,特别是那些需要严格的状态管理和调试工具的应用。
四、React性能比较面试题
1.vue和react的区别?
1)react严格上针对的是mvc模式的view层,vue则是mvvm模式。
2)操作dom的方式不同,vue使用的是指令操作dom,react是通过js进行操作。
3)数据绑定不同,vue实现的是双向绑定,react的数据流动是单向的。
4)react中state是不能直接改变的,需要使用setState改变。vue中的state不是必须的,数据主要是由data属性在vue对象中管理的。
2.react性能优化的方案
(1)重写shouldComponentUpdate来避免不必要的dom操作。
注意:function组件使用useMemo+memo结合优化性能,它们可以避免组件或组件内部的计算在每次渲染时都执行,从而减少不必要的性能开销。
(2)使用 production 版本的react.js。
(3)使用key来帮助React识别列表中所有子组件的最小变化。
3.react项目用过什么脚手架
Mern:MERN是脚手架的工具,它可以很容易地使用Mongo, Express, React and NodeJS生成同构JS应用。它最大限度地减少安装时间,并得到您使用的成熟技术来加速开发。
webpack:单独开文章写笔记
vite:单独开文章写笔记
gulp:。。。
4.介绍一下webpack
webpack是一个前端模块化打包工具,主要由入口,出口,loader,plugins四部分组成。
前端打包工具还有一个gulp,侧重前端开发的过程,而webpack侧重于模块,例如它会把css文件看作一个模块,通过css-loader将css打包成符合css的静态资源。
5.如果你创建了类似于下面的Twitter元素,那么它们相关的类定义是什么?
<Twitter username='tylermcginnis33'>
{(user) => user === null
? <Loading />
: <Badge info={user} />}
</Twitter>
这种模式中,组件会接收某个函数作为其子组件,然后在渲染函数中以 props.children 进行调用:
import React, { Component, PropTypes } from 'react'
import fetchUser from 'twitter'
class Twitter extends Component {
state = {
user: null,
}
static propTypes = {
username: PropTypes.string.isRequired,
}
componentDidMount () {
fetchUser(this.props.username)
.then((user) => this.setState({user}))
}
render () {
return this.props.children(this.state.user)
}
}
这种模式的优势在于将父组件与子组件解耦和,父组件可以直接访问子组件的内部状态而不需要再通过 Props 传递,这样父组件能够更为方便地控制子组件展示的 UI 界面。譬如产品经理让我们将原本展示的 Badge 替换为 Profile,我们可以轻易地修改下回调函数即可:
<Twitter username='tylermcginnis33'>
{(user) => user === null
? <Loading />
: <Profile info={user} />}
</Twitter>
6.为什么我们需要使用react提供的children API而不是Javascript的map?
props.children并不一定是数组类型,譬如下面这个元素:
<Parent> <h1>Welcome.</h1> </Parent>
如果我们使用props.children.map函数来遍历时会受到异常提示,因为在这种情况下props.children是对象(object)而不是数组(array)。
React 当且仅当超过一个子元素的情况下会将props.children设置为数组,就像下面这个代码片:<Parent> <h1>Welcome.</h1> <h2>props.children will now be an array</h2> </Parent>
这也就是我们优先选择使用React.Children.map函数的原因,其已经将props.children不同类型的情况考虑在内了。
createElement 函数是 JSX 编译之后使用的创建 React Element 的函数,而 cloneElement 则是用于复制某个元素并传入新的 Props。
五、React常见的几种钩子
1.useState
useState 是用来解决函数组件中不能定义自己的状态的问题,useState 可以传递一个参数,做为状态的
初始值
,返回一个数组,数组的第一个元素是返回的状态变量
,第二个是修改状态变量的函数
。他的定义格式,第一个是变量名,第二个是改变变量的方法
const [state, setState] = useState(initalState); // 初始化,state可以任意命名 setState(newState); // 修改state的值
为什么React钩子中的useState没有更新状态
React钩子中的useState没有更新状态的原因可能有以下几种:
- 错误的使用方式:useState是React提供的一个钩子函数,用于在函数组件中添加状态。正确的使用方式是通过数组解构赋值获取状态值和更新函数,然后在组件中使用更新函数来改变状态值。如果没有正确使用更新函数,就无法更新状态。
- 异步更新:React中的状态更新是异步的,这意味着在同一个函数调用中多次更新状态时,React可能会将多个更新合并为一个更新,从而导致某些更新被忽略。这种情况下,可以使用函数式更新来确保状态更新是基于最新的状态值进行的。
- 浅比较:React使用浅比较来判断状态是否发生变化,如果新旧状态的引用相同,React会认为状态没有发生变化,从而不会触发重新渲染。如果在更新状态时没有正确地创建新的状态对象,而是直接修改了原始状态对象,就会导致状态没有更新。
- 组件未正确挂载:如果组件未正确挂载到DOM树上,useState的状态更新可能无法生效。确保组件已经正确地挂载到DOM树上,才能正常更新状态。
综上所述,要确保React钩子中的useState能够更新状态,需要:
- 正确使用更新函数
- 避免异步更新问题
- 正确创建新的状态对象
- 并确保组件已正确挂载到DOM树上。
2.useEffect
useEffect 在类组件
中放在 componentDidMount,componentDidUpdate 等执行的请求获取数据的操作,在React Hooks中都可以用 useEffect
来处理,他可以类比类组件中的两个生命周期函数。
useEffect(() => {
// 此处编写 组件挂载之后和组件重新渲染之后执行的代码
...
return () => {
// 此处编写 组件即将被卸载前执行的代码
...
}
}, [dep1, dep2 ...]); // 依赖数组
useEffect和useLayoutEffect的区别?
1)执行时机不同
useEffect:在组件渲染到屏幕之后异步执行。这意味着它不会阻塞浏览器的绘制和更新,适用于大多数与数据获取、订阅事件、手动修改DOM等不会直接影响页面布局和视觉呈现的操作。
useLayoutEffect:会在浏览器进行布局和绘制之前同步执行。useLayoutEffect中执行的操作会修改DOM样式或结构,并且在浏览器绘制之前就完成这些修改,避免页面的重绘和回流带来的性能问题。
注意:useLayoutEffect是在DOM结构更新后、渲染前执行,在渲染时是同步执行,相当于有一个防抖效果。2)对性能影响不同
useEffect:由于是异步执行,不会阻塞页面的渲染,对用户交互的响应性影响较小,但如果副作用操作耗时较长,可能会在用户操作后有短暂的延迟才看到效果。
useLayoutEffect:由于是同步执行,如果在其中执行的操作耗时较长,会阻塞页面的渲染,可能导致页面卡顿,影响用户体验。
3)对渲染的影响不同:
useEffect 的执行不会阻塞浏览器的渲染工作
useLayoutEffect 的执行可能会阻塞浏览器的渲染,使用 useLayoutEffect 时需要注意性能问题。
4) 使用场景不同
一般情况下,如果副作用操作不会影响页面的布局,建议使用useEffect。例如发送网络请求获取数据、添加事件监听器、更新本地存储等。
如果副作用操作会影响页面的布局和视觉呈现,例如直接修改DOM元素的样式、位置、大小等,为了避免页面的闪烁和重绘,建议用useLayoutEffect。
useLayoutEffect会在浏览器进行布局和绘制之前同步执行。这意味着它可以在 DOM 更新后、浏览器绘制之前进行操作,从而避免由于异步的useEffect可能导致的闪烁现象。使用建议:
将直接影响页面布局和视觉呈现的操作放在useLayoutEffect中,例如直接修改 DOM 元素的样式、位置、大小等。因为它会等待这些操作完成后再进行渲染,所以可以避免页面的闪烁。
减少useLayoutEffect中执行的复杂或耗时操作。由于它是同步执行,如果操作过于耗时,可能会阻塞页面的渲染,导致卡顿。如果必须进行复杂计算或耗时操作,可以考虑将其拆分为异步操作,或者在操作完成后再进行必要的 DOM 更新。
精确管理依赖项,只将会影响布局的变量添加到依赖项数组中。这样可以避免不必要的useLayoutEffect
执行。
3.useLayoutEffect
useLayoutEffect 使用方法、所传参数和 useEffect 完全相同。大多数情况下将 useEffect 替换成 useLayoutEffect 完全看不出区别。
唯一区别就是:使用 useEffect 时,页面挂载会出现闪烁。而使用 useLayoutEffect 时页面没有闪烁,是因为 useEffect 是在页面渲染完成后再去更新数据的,所以会出现短暂的闪烁,而 useLayoutEffect 是在页面还没有渲染时就将数据给更新了,所以没有出现闪烁。
注意:大部分情况用useEffect就足够了,useLayoutEffect 会阻塞渲染,所以需要小心的使用。
4.useMemo
useMemo 是为了减少组件重新渲染时不必要的函数计算,可以用来做性能优化。
类似于vue中的计算属性,他的依赖值是后面数组中的值
const memoizedValue = useMemo(() => {
// 计算逻辑
...
// return res;
}, [a, b]);
useMemo 可以传入2个参数,第1个参数为函数,用来进行一些计算,第2个参数是依赖关系(可选参数),返回值为第一个函数 return 出去的值,只有在依赖项发生变化时才会重新执行计算函数进行计算,如果不传依赖项,每次组件渲染都会重新进行计算。// 代码示例 import { useState, useMemo } from 'react' function Demo() { const [num, setNum] = useState(0); const addNum = () => { setNum(num + 100); }; const total = useMemo(() => { console.log('---求和---'); // 求和计算 let temp = 0; for(let i = num; i > 0; i--) { temp += i; } return temp; }, [num]); return ( <div> <button onClick={addNum}>addNum</button> <p>{`num: ${num}`}</p> <p>{`total: ${total}`}</p> </div> ) } export default Demo;
点击修改num的值,total 对应的计算函数会重新执行一遍,因为num是该计算函数的
依赖项
。
5.useContext
在 React 中传递属性只能一层一层传,如果组件结构比较复杂,层级比较深的时候,数据传递起来就比较麻烦,可能会经过很多次的传递才能将属性传递到目标组件中,那么有没有一种可以在全局进行状态共享的实现方法呢?useContext 就是为了解决这个问题的,可以实现不必层层传递就能共享状态的功能。具体用法看下面步骤:
先封装一个js,里面可以设置初始值,这个初始值,可以在任何地方使用
import React from 'react'; // React.createContext()中的参数是默认值,可填可不填 const UserContext = React.createContext( { name: '张三' }); export default UserContext;
在代码中引用封装好的js文件Father:
import React, { useContext } from 'react' import UserContext from './context'; // const UserContext = React.createContext(); function Demo() { // 如果React.createContext没有指定默认值,也可以在对应子组件上套上UserContext.Provider来指定值 return ( // <UserContext.Provider value={{ name: '张三' }}> <Child /> // </UserContext.Provider> ) }
Child:function Child() { const user = useContext(UserContext); return ( <div> <p>{`name: ${user.name}`}</p> </div> ) } export default Demo;
6.useReducer
useReducer 也是用来实现状态管理的 hook,useState 就是基于 useReducer 实现的,useReducer 可以实现比 useState 更复杂的状态管理逻辑。
它可以对多个值进行管理,他的定义方式和useState很像,但是,useState 是基于它实现的,使用时注意定义的参数
// 代码示例
import React, { useReducer } from 'react'
// 1.需要有一个 reducer 函数,第一个参数为之前的状态,第二个参数为行为信息
function reducer(state, action) {
switch (action) {
case 'add':
return state + 1;
case 'minus':
return state - 1;
default:
return 0;
}
}
function Demo() {
// 2.引入useReducer,第一个参数时上面定义的reducer,第二个参数时初始值
// 3.返回为一个数组,第一项为状态值,第二项为一个 dispatch 函数,用来修改状态值
const [count, dispatch] = useReducer(reducer, 0);
return (
<div>
<button onClick={() => { dispatch('add') }} >add</button>
<button onClick={() => { dispatch('minus') }} >minus</button>
<button onClick={() => { dispatch('unknown') }} >unknown</button>
<p>{`count: ${count}`}</p>
</div>
);
}
export default Demo;
7.useRef
useRef 可以帮助我们
获取 dom 和 react 组件实例
,类组件中的React.createRef()
也有相同的功能。除了用 useRef 获取组件实例,还可以用来存储变量的值,但是需要注意的一点是,修改 .current 的值不会触发组件的重新渲染。
8.useCallback
返回一个缓存的回调函数,缓存一个函数类型因变量
useCallback和useMemo的区别:
useCallback 的用法和 useMemo 完全一样,useMemo 返回的是计算函数 return 出去的
值
,而 useCallback 可以理解成返回的是那个计算函数
。
9.React.memo
当父组件发生改变时,默认情况下它的子孙组件也会重新渲染,当某些子组件不需要更新时,也会被强制更新,为了避免这种情况,我们可以使用 React.memo。
类组件中有 shouldComponentUpdate 和 PureComponent 来避免子组件做不必要的渲染。
函数组件中的 React.memo() 也有类似的功能,它和 PureComponent 类似,但是只适用于函数组件,默认情况下仅对 props 进行一个浅比较来决定要不要更新,复杂情况下支持自己手写对比的逻辑。
function Demo(props) {
// ...
}
function compare(prevProps, nextProps) {
// 自己写对比逻辑,返回 true 更新,false 跳过更新
// return false
}
export default React.memo(Demo, compare)
- 参数1:组件要渲染的内容。
- 参数2:写的回调函数,一般情况下都在props传过来的数据为引用类型,才需要手动来判断,如果是基本类型则不需要写参数2来手动判断。
如果是引用类型,可以用下面方法进行比较
const Child = React.memo(
({ count }) => {
return (
<div>
<h3>child组件 -- {count.n}</h3>
</div>
)
},
// 使用lodash库来完成对象的值的比较,从而用来完成减少组件的无用的重复渲染
(prevProps, nextProps) => _.isEqual(prevProps, nextProps)
)
10.forwardRef
forwardRef 可以在
父组件中操作子组件的 ref 对象
,并且将 ref 对象作为一个参数
传递给了子组件。forwardRef可以结合useImperativeHandle钩子使用,暴露值和方法给父组件使用。
加上forwardRef后子组件就多了个入参ref,来绑定对应子组件的dom元素。
11.总结:
可以将他们分为两个部分(其中useRef 不属于两者):
11.1自变量:本身是可以改变从而影响其他
1) useState 定义自变量
2) useReducer 用于操作多个自变量(一个层级的自变量和因变量也可以成为其他层级的自变量和因变量)
3) useContext 可以跨层级的传递自变量
11.2 因变量:因自变量改变而导致自己改变
1) useMemo 和 useCallback(缓存函数类型因变量) 定义无副作用的因变量
2) useEffect 定义有副作用(可以进行一些操作,比如修改某些参数,请求数据,操作dom等)的因变量
3) useReducer 可看成是一个进阶版的useState ,借用redux的设计理念,将多个state合并成一个,本质上也是一个因变量。
4) useRef 可以操作子组件中的方法,也可以作为一个保存数值的标记,在路径中起到缓存数据的作用
更多可见文档地址:
https://zh-hans.react.dev/reference/react/hooks
六、高级指引:
1.无障碍
2.代码分割
3.Context
4.错误边界
5.Refs转发
6.Fragments
7.高阶组件
8.与第三方组件协同
9.深入jsx
10.性能优化
11.Portals
12.Profiler
13.不使用es6
14.不使用jsx
15.协调
16.Refs&DOM
17.Render Props
18.静态类型检查
19.严格模式
20.使用propTypes类型检查
21.非受控组件
22.Web Components
更多可参考文档地址:
https://zh-hans.react.dev/reference/react
七、react18的新特性
兼容性:
不支持ie浏览器
异步同步:
异步更新
默认情况下,setState方法会以异步方式进行更新,着意味着它会将多个状态更新批量处理,并在适当的时机进行合并和应用,以优化性能,减少不必要的渲染,并提高应用程序的响应性
this.setState({ count: this.state.count + 1 });
同步更新(flushSync)
flushSync强制执行同步更新
import { flushSync } from 'react-dom'; flushSync(() => { this.setState({ count: this.state.count + 1 }); });
通过使用
flushSync
包裹setState
的调用,您可以确保在执行下一个任务之前立即获取到更新后的状态。注意:
- 使用
flushSync
可能会对性能产生影响,并且应谨慎使用,以避免阻塞主线程。- 还可以使用
useTransition
提供的优先级控制来平衡不同任务之间的更新。
React新增的api:
1.startTransition:
startTransition优化应用程序的性能和用户体验
使用场景:
- 告诉react在下次重新渲染时,应该延迟更新组件
- 一些较慢的操作(比如异步请求等)就可以放在后台执行,不会影响应用程序的交互功能
2.useTransition
useTransition
是startTransition
的 hook 版本。它可以在函数组件中使用,从而让开发者更方便地控制异步操作的状态。使用场景:
我们使用
useTransition
hook 来控制异步请求的状态,并在加载数据时显示一个 Loading... 的提示信息。
3.createRoot
使用
createRoot
函数来创建根 React 组件,并将其渲染到页面上。这样,我们就可以使用多个根节点来构建各种复杂的应用程序界面。使用场景:
使用
createRoot
函数来创建根 React 组件,并将其渲染到页面上。这样,我们就可以使用多个根节点来构建各种复杂的应用程序界面。
4.useDeferredValue
useDeferredValue
是一个新的 hook,可以将某个状态值的更新延迟一段时间后再执行,从而提高应用程序的性能和用户体验。使用场景:
我们使用
useDeferredValue
hook 将搜索词的更新延迟了一秒钟。这样,用户在快速输入搜索词时,不会因为频繁的重新渲染而出现卡顿等问题。
5. useTransition
使用场景:
useTransition
hook,用于帮助开发者控制异步操作的状态。
startTransition
的调用:在handleSearch
函数中,我们使用startTransition
包裹了异步数据加载的逻辑。这样,React 会将这个更新标记为“可中断”,并在用户界面空闲时处理。isPending
状态:isPending
状态用于指示当前是否有正在进行的过渡操作。在数据加载期间,isPending
为true
,显示“Loading...”提示;数据加载完成后,isPending
变为false
,显示加载的数据。通过这种方式,你可以确保用户在输入搜索词时,界面仍然保持响应,不会因为数据加载而卡顿。
eg1(搜索):
import React, { useState, useTransition } from 'react'; const Test: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); const [isPending, startTransition] = useTransition(); const [data, setData] = useState(null); const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => { const value = e.target.value; setSearchTerm(value); startTransition(() => { // 模拟异步数据加载 new Promise((resolve) => { setTimeout(() => { resolve(`Data for ${value}`); }, 2000); }).then((result) => { setData(result); }); }); }; return ( <div> <input type="text" value={searchTerm} onChange={handleSearch} /> <p>Search Term: {searchTerm}</p> <p>{isPending ? 'Loading...' : data}</p> </div> ); }; export default Test;
eg2(拉进度条+渲染页面):
可以明显发现使用了
useTransition钩子的时候页面明显更流畅
import React, { useTransition, useState, useMemo } from 'react' export default function UseTransition() { const [isPending, startTransition] = useTransition() const [rangeValue, setRangeValue] = useState(1) const [renderData, setRenderData] = useState([1]) const [isStartTransition, setIsStartTransition] = useState(false) const handleChange = (e) => { setRangeValue(e.target.value) const arr = [] arr.length = e.target.value for (let i = 0; i <= arr.length; i++) { arr.fill(i, i + 1) } if (isStartTransition) { startTransition(() => { setRenderData(arr) }) } else { setRenderData(arr) } } const jsx = useMemo(() => { return renderData.map((item) => { return ( <div style={{ width: 20, height: 20, backgroundColor: `#${Math.floor(Math.random() * 22222222).toString( 16 )}`, margin: 10, display: 'inline-block', }} > {item} </div> ) }) }, [renderData]) return ( <div> <div style={{ textAlign: 'center' }}> <label> <input type="checkbox" checked={isStartTransition} onChange={(e) => { setIsStartTransition(e.target.checked) }} /> useTransition </label> <input type="range" value={rangeValue} min={0} max={10000} style={{ width: 120 }} onChange={handleChange} /> <span>进度条 {rangeValue}</span> <span>{isPending?'loading':''}</span> <hr /> </div> {jsx} </div> ) }
6. useMutableSource
useMutableSource
是一个新的 hook,用于获取可变数据源,并可以在多个组件之间共享状态。它可以帮助开发者拆分组件逻辑,并使其更加灵活和可复用。使用场景:
使用
myDataSource
作为可变数据源,并将其共享到多个组件中。在Counter
组件中,我们订阅了数据源的更新,并实时反映出计数器的变化。在App
组件中,我们使用了useMutableSource
hook 来获取数据源的值,从而实现了多组件之间的状态共享。
七、React实战题
1.关键字高亮
2.轮播图
react轮播图示例-优快云博客
3.mobx使用示例
1)安装必要的依赖
你已经安装了
mobx
和mobx-react
,所以这一步可以跳过。如果你还没有安装,可以使用以下命令:npm install mobx mobx-react
2) 创建 Store
首先,创建一个 MobX store 来管理应用的状态。
src/stores/CounterStore.ts
import { makeAutoObservable } from 'mobx'; class CounterStore { count = 0; constructor() { makeAutoObservable(this); } increment = () => { this.count += 1; }; decrement = () => { this.count -= 1; }; } const counterStore = new CounterStore(); export default counterStore;
3)创建 React 组件
接下来,创建一个 React 组件来使用这个 store。
src/components/Counter.tsx
import React from 'react'; import { observer } from 'mobx-react'; import counterStore from '../stores/CounterStore'; const Counter: React.FC = observer(() => { return ( <div> <h1>Count: {counterStore.count}</h1> <button onClick={counterStore.increment}>Increment</button> <button onClick={counterStore.decrement}>Decrement</button> </div> ); }); export default Counter;
4) 在 App 组件中使用 Counter 组件
最后,在
App
组件中引入并使用Counter
组件。
src/App.tsx
import React from 'react'; import Counter from './components/Counter'; const App: React.FC = () => { return ( <div className="App"> <header className="App-header"> <h1>React + MobX Counter Example</h1> <Counter /> </header> </div> ); }; export default App;
5) 运行项目
确保你的项目配置正确,然后运行项目:
npm start
详细解释
创建 Store:
- CounterStore:定义了一个
count
属性和两个方法increment
和decrement
。- makeAutoObservable:使
CounterStore
中的所有属性和方法都自动可观察。创建 React 组件:
- Counter:使用
observer
高阶组件包裹,使组件能够响应 store 的变化。- count:从
counterStore
中读取当前的计数值。- increment 和 decrement:绑定到
counterStore
的相应方法,更新计数值。在 App 组件中使用 Counter 组件:
- App:引入并使用
Counter
组件。
八、precommit配置
1.babelrc配置
.babelrc
{ "presets": [ "@babel/preset-react", "@babel/preset-typescript", "@babel/preset-env" ], "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], "@babel/plugin-proposal-nullish-coalescing-operator", "@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-class-properties", "@babel/plugin-proposal-object-rest-spread", "@babel/plugin-transform-runtime" ] }
2.eslintrc配置
.eslintrc.js
module.exports = { "parser": "@typescript-eslint/parser", "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended" ], "plugins": ["react", "react-hooks", "@typescript-eslint"], "settings": { "react": { "version": "16.8" // 指定 React 版本 } }, "rules": { "react-hooks/rules-of-hooks": "error", // 检查 Hooks 的使用 "react-hooks/exhaustive-deps": "warn", // 检查 useEffect 等 Hook 的依赖项 "prefer-const": "off", // 关闭 prefer-const 规则 "@typescript-eslint/explicit-function-return-type": "off", // 关闭 explicit-function-return-type 规则 "react/prop-types": "off", // 关闭 prop-types 规则 "@typescript-eslint/no-unused-vars": "off", // 关闭 no-unused-vars 规则 "@typescript-eslint/no-use-before-define": "off", // 关闭 no-use-before-define 规则 "react/display-name": "off", // 关闭 display-name 规则 "no-prototype-builtins": "off", // 关闭 no-prototype-builtins 规则 "@typescript-eslint/camelcase": "off" // 关闭 camelcase 规则 }, "env": { "browser": true, "es6": true }, "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", "ecmaFeatures": { "jsx": true } } }
3.prettierrc配置
.prettierrc.js
{ "dependencies": { "express": "^4.21.1" }, "scripts": { "start": "node server.js" } }
4.stylelintrc配置
.stylelintrc.js
module.exports = { overrides: [ { customSyntax: 'postcss-scss', files: ['**/*.css', '**/*.scss'] }, { customSyntax: 'postcss-less', files: ['**/*.less'] }, { customSyntax: 'postcss-html', files: ['**/*.html', '**/*.vue', '**/*.nvue'] } ], plugins: ['stylelint-order'], rules: { // 禁止未知单位 'unit-no-unknown': null, // 为适用的颜色功能指定现代或传统符号 'color-function-notation': 'legacy', // 禁止无效的十六进制颜色 'color-no-invalid-hex': true, // 不允许未知的规则 'at-rule-no-unknown': [ true, { ignoreAtRules: [ 'content', 'each', 'error', 'extend', 'for', 'function', 'if', 'include', 'mixin', 'return', 'while', 'tailwind', 'apply', 'variants', 'responsive', 'screen' ] } ], 'order/properties-order': [ { // Must be first. properties: ['all'] }, { // Position. properties: [ 'position', 'inset', 'inset-block', 'inset-inline', 'top', 'right', 'bottom', 'left', 'z-index' ] }, { // Display mode. properties: ['box-sizing', 'display'] }, { // Flexible boxes. properties: [ 'flex', 'flex-basis', 'flex-direction', 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap' ] }, { // Grid layout. properties: [ 'grid', 'grid-area', 'grid-template', 'grid-template-areas', 'grid-template-rows', 'grid-template-columns', 'grid-row', 'grid-row-start', 'grid-row-end', 'grid-column', 'grid-column-start', 'grid-column-end', 'grid-auto-rows', 'grid-auto-columns', 'grid-auto-flow', 'grid-gap', 'grid-row-gap', 'grid-column-gap' ] }, { // Gap. properties: ['gap', 'row-gap', 'column-gap'] }, { // Layout alignment. properties: [ 'place-content', 'place-items', 'place-self', 'align-content', 'align-items', 'align-self', 'justify-content', 'justify-items', 'justify-self' ] }, { // Order. properties: ['order'] }, { // Box model. properties: [ 'float', 'width', 'min-width', 'max-width', 'height', 'min-height', 'max-height', 'aspect-ratio', 'padding', 'padding-block', 'padding-block-start', 'padding-block-end', 'padding-inline', 'padding-inline-start', 'padding-inline-end', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-block', 'margin-block-start', 'margin-block-end', 'margin-inline', 'margin-inline-start', 'margin-inline-end', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'overflow', 'overflow-x', 'overflow-y', '-webkit-overflow-scrolling', '-ms-overflow-x', '-ms-overflow-y', '-ms-overflow-style', 'overscroll-behavior', 'overscroll-behavior-x', 'overscroll-behavior-y', 'overscroll-behavior-inline', 'overscroll-behavior-block', 'clip', 'clip-path', 'clear' ] }, { // Typography. properties: [ 'font', 'font-family', 'font-size', 'font-variation-settings', 'font-style', 'font-weight', 'font-feature-settings', 'font-optical-sizing', 'font-kerning', 'font-variant', 'font-variant-ligatures', 'font-variant-caps', 'font-variant-alternates', 'font-variant-numeric', 'font-variant-east-asian', 'font-variant-position', 'font-size-adjust', 'font-stretch', 'font-effect', 'font-emphasize', 'font-emphasize-position', 'font-emphasize-style', '-webkit-font-smoothing', '-moz-osx-font-smoothing', 'font-smooth', 'hyphens', 'line-height', 'color', 'text-align', 'text-align-last', 'text-emphasis', 'text-emphasis-color', 'text-emphasis-style', 'text-emphasis-position', 'text-decoration', 'text-decoration-line', 'text-decoration-thickness', 'text-decoration-style', 'text-decoration-color', 'text-underline-position', 'text-underline-offset', 'text-indent', 'text-justify', 'text-outline', '-ms-text-overflow', 'text-overflow', 'text-overflow-ellipsis', 'text-overflow-mode', 'text-shadow', 'text-transform', 'text-wrap', '-webkit-text-size-adjust', '-ms-text-size-adjust', 'letter-spacing', 'word-break', 'word-spacing', 'word-wrap', // Legacy name for `overflow-wrap` 'overflow-wrap', 'tab-size', 'white-space', 'vertical-align', 'list-style', 'list-style-position', 'list-style-type', 'list-style-image', 'src', 'font-display', 'unicode-range', 'size-adjust', 'ascent-override', 'descent-override', 'line-gap-override' ] }, { // Accessibility & Interactions. properties: [ 'pointer-events', '-ms-touch-action', 'touch-action', 'cursor', 'visibility', 'zoom', 'table-layout', 'empty-cells', 'caption-side', 'border-spacing', 'border-collapse', 'content', 'quotes', 'counter-reset', 'counter-increment', 'resize', 'user-select', 'nav-index', 'nav-up', 'nav-right', 'nav-down', 'nav-left' ] }, { // Background & Borders. properties: [ 'background', 'background-color', 'background-image', "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", 'filter:progid:DXImageTransform.Microsoft.gradient', 'filter:progid:DXImageTransform.Microsoft.AlphaImageLoader', 'filter', 'background-repeat', 'background-attachment', 'background-position', 'background-position-x', 'background-position-y', 'background-clip', 'background-origin', 'background-size', 'background-blend-mode', 'isolation', 'border', 'border-color', 'border-style', 'border-width', 'border-block', 'border-block-start', 'border-block-start-color', 'border-block-start-style', 'border-block-start-width', 'border-block-end', 'border-block-end-color', 'border-block-end-style', 'border-block-end-width', 'border-inline', 'border-inline-start', 'border-inline-start-color', 'border-inline-start-style', 'border-inline-start-width', 'border-inline-end', 'border-inline-end-color', 'border-inline-end-style', 'border-inline-end-width', 'border-top', 'border-top-color', 'border-top-style', 'border-top-width', 'border-right', 'border-right-color', 'border-right-style', 'border-right-width', 'border-bottom', 'border-bottom-color', 'border-bottom-style', 'border-bottom-width', 'border-left', 'border-left-color', 'border-left-style', 'border-left-width', 'border-radius', 'border-start-start-radius', 'border-start-end-radius', 'border-end-start-radius', 'border-end-end-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius', 'border-image', 'border-image-source', 'border-image-slice', 'border-image-width', 'border-image-outset', 'border-image-repeat', 'outline', 'outline-width', 'outline-style', 'outline-color', 'outline-offset', 'box-shadow', 'mix-blend-mode', 'filter:progid:DXImageTransform.Microsoft.Alpha(Opacity', "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", 'opacity', '-ms-interpolation-mode' ] }, { // SVG Presentation Attributes. properties: [ 'alignment-baseline', 'baseline-shift', 'dominant-baseline', 'text-anchor', 'word-spacing', 'writing-mode', 'fill', 'fill-opacity', 'fill-rule', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'flood-color', 'flood-opacity', 'image-rendering', 'lighting-color', 'marker-start', 'marker-mid', 'marker-end', 'mask', 'shape-rendering', 'stop-color', 'stop-opacity' ] }, { // Transitions & Animation. properties: [ 'transition', 'transition-delay', 'transition-timing-function', 'transition-duration', 'transition-property', 'transform', 'transform-origin', 'animation', 'animation-name', 'animation-duration', 'animation-play-state', 'animation-timing-function', 'animation-delay', 'animation-iteration-count', 'animation-direction' ] } ] } }
5.commitlintrc.json配置
.commitlintrc.json
{ "extends": ["@commitlint/config-conventional"], "rules": { "type-enum": [ 2, "always", [ "feat", "fix", "docs", "style", "refactor", "test", "chore", "ci" ] ], "subject-empty": [2, "never"], "type-empty": [2, "never"] } }
6.package.json配置
{
"name": "yunwei_",
"version": "3.0.0",
"private": true,
"dependencies": {
"@ant-design/icons": "^4.3.0",
"ace-builds": "^1.4.13",
"antd": "4.21.5",
"axios": "^1.5.1",
"bizcharts": "^3.5.9",
"history": "^4.10.1",
"lodash": "^4.17.19",
"mobx": "^5.15.6",
"mobx-react": "^6.3.0",
"moment": "^2.24.0",
"react": "^16.13.1",
"react-ace": "^9.5.0",
"react-dev-utils": "^10.2.1",
"react-dom": "^16.13.1",
"react-router-dom": "^5.2.0",
"react-script": "^2.0.5",
"strip-ansi": "^7.1.0",
"xterm": "^4.6.0",
"xterm-addon-fit": "^0.5.0"
},
"scripts": {
"dev:build": "cross-env NODE_ENV=development webpack --config config/webpack.config.js",
"start:dev": "cross-env NODE_ENV=development webpack-dev-server --config config/webpack.config.js",
"start:prod": "cross-env NODE_ENV=production webpack-dev-server --config config/webpack.config.js",
"dll": "webpack --config config/webpack.dll.config.js",
"build": "cross-env NODE_ENV=production webpack --config config/webpack.config.js",
"build:analyze": "cross-env NODE_ENV=production webpack --config config/webpack.config.js --analyze"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/plugin-proposal-class-properties": "7",
"@babel/plugin-proposal-decorators": "^7.10.5",
"@babel/plugin-proposal-nullish-coalescing-operator": "7",
"@babel/plugin-proposal-object-rest-spread": "7",
"@babel/plugin-proposal-optional-chaining": "7",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "7",
"@babel/preset-env": "^7.22.20",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@types/react": "^16.9.50",
"@types/react-dom": "^16.9.8",
"autoprefixer": "^9.7.3",
"babel-loader": "8",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"css-loader": "3.6.0",
"file-loader": "^5.0.2",
"happypack": "^5.0.1",
"html-webpack-plugin": "^3.2.0",
"less": "3.13.1",
"less-loader": "5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "3.0.0",
"prop-types": "^15.8.1",
"style-loader": "^1.0.1",
"terser-webpack-plugin": "^2.2.2",
"typescript": "^5.2.2",
"url-loader": "^3.0.0",
"webpack": "^4.41.2",
"webpack-bundle-analyzer": "^4.10.2",
"webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.9.0",
"webpack-merge": "^4.2.2",
"webpack-parallel-uglify-plugin": "^1.1.2",
"webpackbar": "^6.0.1"
}
}