32、说说react 中jsx语法糖的本质?
JSX(JavaScript XML)是React中使用的一种语法扩展,它允许我们在JavaScript代码中编写类似于HTML的结构。JSX看起来像是将HTML直接嵌入到JavaScript代码中,但它实际上是一种语法糖,最终会被转译为普通的JavaScript代码。
下面是我对JSX语法糖的本质的理解:
- 转译过程:在使用JSX编写的React代码被传递给Babel等工具进行转译时,会将JSX转化为等价的纯JavaScript代码。这个过程是通过将JSX表达式中的标签和属性转化为函数调用和对象的表示方式来完成的。
- React.createElement()方法:JSX的本质可以看作是React.createElement()方法的调用。当编写JSX时,实际上是在创建React元素(Element),而不是直接创建实际的DOM节点。React.createElement()接收三个参数:类型(标签名或组件)、属性对象和子元素,然后返回一个描述虚拟DOM结构的对象。
例如,下面的JSX代码:
const element = <h1 className="title">Hello, World!</h1>;
会被转译为以下的JavaScript代码:
const element = React.createElement("h1", { className: "title" }, "Hello, World!");
- JSX表达式的嵌入:JSX允许我们在大括号内嵌入任意JavaScript表达式。这使得我们可以在JSX中使用变量、函数等来动态地生成内容和属性。在转译过程中,JSX表达式会被替换为普通的JavaScript表达式,并将其求值后作为属性值或子元素进行传递。
例如,下面的JSX代码:
const name = "Alice"; const element = <h1>Hello, {name}!</h1>;
会被转译为以下的JavaScript代码:
const name = "Alice"; const element = React.createElement("h1", null, "Hello, ", name, "!");
总结起来,JSX是React提供的一种语法糖,它允许我们在JavaScript代码中编写类似于HTML的结构。JSX最终会被转译为React.createElement()方法的调用,创建描述虚拟DOM结构的对象。这种转译过程使得我们可以更直观地编写React组件和界面,同时保留了JavaScript的灵活性和表达能力。
33、pops和state相同点和不同点?render方法在哪些情况下会执行?
Props(属性)和 State(状态)是 React 组件中用于管理数据的两个概念,它们有一些相同点和不同点。
相同点:
- 数据管理:Props 和 State 都用于管理组件中的数据,并且可以影响组件的行为和渲染结果。
- 可变性:Props 和 State 都可以被修改,当它们发生变化时,React 会重新渲染组件以反映新的数据。
不同点:
- 归属和来源:Props 是由父组件传递给子组件的,子组件无法直接修改 Props;而 State 是组件自身内部管理的数据,可以通过 setState() 方法来修改。
- 初始化方式:Props 是在组件创建时由父组件传递的,不可直接修改;State 是在组件内部通过构造函数或 useState() 钩子进行初始化,并且可以随后修改。
- 更新方式:Props 是从外部传递给组件的,当父组件重新渲染时可能会更新 Props;State 是在组件内部状态的变化触发的,通过调用 setState() 方法来更新。
- 生命周期:Props 在组件的整个生命周期中保持不变;State 可以根据组件的各个生命周期阶段进行更新。
- 对应关系:Props 通常用于向子组件传递数据和配置信息;State 通常用于存储和管理组件自身的状态和数据。
关于 render()
方法的执行情况:
render()
方法是 React 组件中必须实现的一个生命周期方法,在组件初次渲染和每次更新时都会执行。- 当组件的 Props 或 State 发生变化时,React 会重新调用组件的
render()
方法来计算新的虚拟 DOM 树,然后对比新旧 DOM 树的差异,并将差异更新到真实 DOM 上。 render()
方法的执行并不意味着实际的 DOM 更新,而是生成一个对应的虚拟 DOM。- 只有在
render()
方法中返回的虚拟 DOM 与上一次的虚拟 DOM 有差异时,React 才会进行实际的 DOM 更新。
需要注意的是,React 也有一些优化策略,例如使用了虚拟 DOM 的 Diff 算法,可以避免不必要的 DOM 更新,以提高性能。
34、redux本来是同步的,为什么它能执行异步代码?实现原理是什么?中间件的 实现原理是什么?
Redux 本身是一个同步的状态管理库,但通过使用中间件,可以使 Redux 支持执行异步代码。实现异步操作的关键在于中间件的功能和机制。
实现原理:
- Reducer:Redux 核心概念之一是 Reducer,它是一个纯函数,根据当前的 state 和 action 返回新的 state。Reducer 是同步执行的,只能处理同步的 action,并且不能引入副作用。
- 中间件:Redux 中间件是一个位于 action 被发起和达到 Reducer 之间的扩展点。中间件可以拦截、分派、修改 action,并可以执行异步操作或其他副作用。
- 异步流程控制:通过中间件,我们可以在异步 action 中编写和处理异步操作,例如网络请求、定时器等。中间件通过拦截特定的 action 类型,监听并执行异步操作,在异步操作完成后再继续派发相应的同步 action。
- next 函数:中间件的实现原理基于洋葱模型,每个中间件都需要接收两个参数:
store.dispatch
和next
。next
是一个函数,它代表下一个中间件链或最终的 dispatch 方法。中间件在处理完自己的逻辑后,可以选择调用next(action)
,将 action 传递给下一个中间件,或者中止中间件链。 - 组合中间件:Redux 允许我们使用
applyMiddleware
函数来组合多个中间件。这样,当一个 action 被派发时,它会依次经过中间件链,每个中间件都有机会处理该 action,或者将其传递给下一个中间件。
总结来说,Redux 通过中间件的机制使得同步的 Redux 可以处理异步操作。中间件拦截、处理异步 action,执行相应的异步操作,并在异步操作完成后继续派发同步的 action 到 Reducer。中间件通过 next 函数和组合中间件的方式实现了洋葱模型,在 Redux 的工作流程中起到连接和扩展的作用。
35、redux中同步action与异步action最大的区别是什么?
在 Redux 中,同步 action 和异步 action 的最大区别在于它们的执行时机和返回值。
- 同步 Action:
- 同步 action 是指没有涉及到具体异步操作的 action。
- 同步 action 可以直接被触发和执行,而不需要等待异步操作的完成。
- 同步 action 的执行是立即发生的,并且返回一个包含
type
属性和其他需要的数据的普通对象。 - 同步 action 可以用来表示应用中的状态变化,例如用户触发的点击、输入等行为。
- 异步 Action:
- 异步 action 是指涉及到具体异步操作(如网络请求、定时器、WebSocket 等)的 action。
- 异步 action 通常不会立即执行,而是需要等待异步操作的完成后才能触发相应的动作。
- 异步 action 在执行过程中可以分为多个阶段,例如开始请求、请求成功、请求失败等。
- 异步 action 的返回值通常是一个函数、Promise 对象或带有特定标识符的对象,用于表示异步操作的进行和结果。
总结来说,同步 action 和异步 action 的最大区别在于执行时机和返回值。同步 action 是立即执行的、返回普通对象表示状态变化;而异步 action 则需要等待异步操作完成后再触发,通常返回函数、Promise 对象或带有特定标识符的对象表示异步操作的进行和结果。为了处理异步操作,Redux 提供了中间件(如 Redux-Thunk、Redux-Saga)来管理和处理异步 action 的执行。
36、redux-saga和redux-thunk的区别与使用场景?
Redux-Saga 和 Redux-Thunk 是两个常用的中间件,用于处理 Redux 中的异步操作。它们在处理异步流程的方式上有一些区别,并且适用于不同的使用场景。
- Redux-Thunk:
- Redux-Thunk 是 Redux 官方推荐的中间件之一,采用的是函数的方式来处理异步操作。
- 在 Redux-Thunk 中,action 可以是一个普通的对象,也可以是一个函数。当 action 是一个函数时,Redux-Thunk 会识别并调用这个函数,允许我们在函数内部进行异步操作,例如发起 API 请求。
- Redux-Thunk 提供了
dispatch
和getState
两个参数给异步函数,使得我们能够在异步函数内部发起其他 action 或获取当前的 state。 - Redux-Thunk 只需引入少量的代码变动,易于上手和使用。
Redux-Thunk 的使用场景:
- 简单的异步操作,如发送网络请求获取数据。
- 不需要复杂的控制流程,只需要在异步操作完成后更新 Redux store。
- Redux-Saga:
- Redux-Saga 是另一个常用的 Redux 中间件,采用的是基于 Generator 函数的方式来处理异步操作。
- 在 Redux-Saga 中,我们可以创建叫做 Sagas 的独立任务,用于管理和监听 Redux action,并在特定的 action 被触发时执行相应的异步操作。
- Redux-Saga 提供了很多帮助函数来处理异步流程,例如
takeEvery
、takeLatest
等,使得我们能够更灵活地控制异步操作的顺序和执行方式。 - Redux-Saga 使用 Generator 函数的特性,使得代码结构更加清晰、易于测试和维护。
Redux-Saga 的使用场景:
- 复杂的异步操作,如多个异步操作的组合、依赖和并发控制。
- 需要更高级的控制流程,如条件触发、取消操作等。
- 需要对副作用进行更细粒度的控制,例如延迟任务、轮询、动画等。
总结来说,Redux-Thunk 适合简单的异步操作,易于上手和使用;Redux-Saga 则适用于复杂的异步操作和流程控制,提供了更高级的解决方案。选择使用哪个中间件取决于具体的应用需求和开发团队的技术偏好。
37、在使用redux过程中,如何防止定义的action-type的常量重复?
在使用 Redux 过程中,避免定义的 action type 常量重复的一种常用做法是使用一个单独的文件来存放这些常量,并进行统一管理。这样可以确保常量的唯一性,并方便进行维护和管理。
下面是一个示例的做法:
- 在项目中创建一个名为
constants.js
的新文件。 - 在该文件中定义你的 action type 常量,以变量的形式进行命名。
// constants.js export const ADD_TODO = 'ADD_TODO'; export const DELETE_TODO = 'DELETE_TODO'; export const UPDATE_TODO = 'UPDATE_TODO'; // 其他常量...
- 在需要使用这些常量的文件中,通过引入
constants.js
来使用这些常量。
import { ADD_TODO, DELETE_TODO, UPDATE_TODO } from './constants'; // 使用常量 const addAction = { type: ADD_TODO, payload: 'New todo' }; const deleteAction = { type: DELETE_TODO, payload: 1 }; const updateAction = { type: UPDATE_TODO, payload: { id: 1, text: 'Updated todo' } }; // 其他使用...
通过将所有的 action type 常量放在一个统一的文件中,可以避免定义的常量重复。此外,使用常量作为 action type,还能提高代码的可读性和维护性,方便在不同的地方(如 reducer、action creator 等)引用和修改。
另外,有一些第三方的库和工具也可以用于自动生成 action type 常量,例如 redux-actions
库中的 createAction
和 handleActions
方法,以及 TypeScript 的 enum
等。这些工具可以更进一步简化常量的定义和管理,提高开发效率和代码质量。
38、React的路由的原理是什么,写出其实现的核心代码?
React 的路由是通过使用第三方库(例如 React Router)来实现的。React Router 是一个常用的用于处理前端路由的库,它提供了一系列的组件和 API 来管理应用程序的路由。
React Router 的原理基于浏览器的 History API(pushState
、replaceState
和 popstate
事件),它允许我们在不刷新页面的情况下更改 URL,并且能够监听 URL 的变化。React Router 利用 History API 提供的功能,实现了前端路由的核心机制。
下面是 React Router 的核心代码示例:
import React from 'react'; import { BrowserRouter as Router, Route, Link } from 'react-router-dom'; // 定义组件 const Home = () => <h2>Home</h2>; const About = () => <h2>About</h2>; const Contact = () => <h2>Contact</h2>; // 路由配置 const App = () => ( <Router> <div> <nav> <ul> <li> <Link to="/">Home</Link> </li> <li> <Link to="/about">About</Link> </li> <li> <Link to="/contact">Contact</Link> </li> </ul> </nav> <Route path="/" exact component={Home} /> <Route path="/about" component={About} /> <Route path="/contact" component={Contact} /> </div> </Router> ); export default App;
在上述代码中,我们导入了相关的 React Router 组件,包括 BrowserRouter
(用于提供路由功能)、Route
(定义单个路由)和 Link
(创建路由链接)。我们定义了三个组件:Home
、About
和 Contact
,分别对应不同的路由。
通过 BrowserRouter
组件将整个应用程序进行包裹,并在 <nav>
中使用 Link
组件创建导航链接。Route
组件定义了具体的路由规则,使用 path
属性指定 URL 匹配规则,使用 component
属性指定对应的组件。
当用户点击导航链接时,React Router 会根据设置的路由规则匹配对应的组件,并将其渲染到页面上,同时改变 URL,但不会触发页面的刷新。这样,我们就实现了前端路由的效果。
需要注意的是,React Router 还提供了其他功能,如动态路由参数、嵌套路由、重定向等。上述代码只是一个简单的示例,你可以根据具体需求进行配置和扩展。
39、React render方法的原理,在什么时候会触发?
在 React 中,render
方法是类组件中的一个生命周期方法,用于将组件的虚拟 DOM 树渲染到真实的 DOM 中。
render
方法是必须实现的方法,它负责定义组件的结构和内容,并返回一个表示组件的虚拟 DOM 树(通常是 JSX 元素)。React 会将这个虚拟 DOM 树与之前的虚拟 DOM 树进行比较,找出差异并更新真实的 DOM。
render
方法会在以下情况下触发:
- 组件首次渲染:当组件首次被挂载到 DOM 树上时,即调用
ReactDOM.render()
或ReactDOM.hydrate()
方法时,会触发组件的render
方法进行首次渲染。 - 组件更新:当组件的状态或属性发生改变时,React 会重新调用组件的
render
方法来生成新的虚拟 DOM 树,并通过栈调和算法(或 Fiber 调和算法)比较新旧虚拟 DOM 树的差异,从而更新真实的 DOM。
需要注意的是,React 有一些优化机制可以避免不必要的 render
调用。React 会比较新旧虚拟 DOM 树的差异,只更新真正发生变化的部分,而对于没有变化的部分则不会重新渲染。这个过程被称为“调和”。这样可以提高性能,避免不必要的 DOM 操作。
在类组件中,除了 render
方法外,还有其他生命周期方法,例如 componentDidMount
、componentDidUpdate
等,可以用来处理组件的副作用、更新后的操作等。但无论是哪个生命周期方法,render
方法都是其基础,它决定了组件的结构和内容,并触发组件的渲染过程。
40、说说reduce方法的作用?自己手动封装一个reduce,写出其核心代码?
reduce 方法是 JavaScript 数组的一个高阶函数,用于对数组中的所有元素进行累积操作,最终返回一个结果。
reduce 方法接受两个参数:一个回调函数和一个初始值。回调函数可以接受四个参数:累积值(上一次回调函数的返回值或初始值)、当前值、当前索引和原数组。回调函数会依次处理数组中的每个元素,并将处理结果作为下一次回调函数的累积值传入。最后,reduce 方法返回最终的累积值。
下面是手动封装一个 reduce 方法的核心代码:
function myReduce(arr, callback, initialValue) { let accumulator = initialValue; for (let i = 0; i < arr.length; i++) { accumulator = callback(accumulator, arr[i], i, arr); } return accumulator; }
这个自定义的 myReduce
方法接受三个参数:一个数组 arr
、一个回调函数 callback
和一个初始值 initialValue
。它使用一个 for
循环遍历数组中的每个元素,每次迭代都将当前元素和累积值传递给回调函数,并将回调函数的返回值更新为新的累积值。最后返回最终的累积值。
你可以根据需要自行使用 myReduce
方法,例如对数组求和、查找最大值、连接字符串等操作。示例用法如下:
const numbers = [1, 2, 3, 4, 5]; const sum = myReduce(numbers, (accumulator, currentValue) => accumulator + currentValue, 0); console.log(sum); // 输出:15 const max = myReduce(numbers, (accumulator, currentValue) => Math.max(accumulator, currentValue), -Infinity); console.log(max); // 输出:5 const str = myReduce(numbers, (accumulator, currentValue) => accumulator + currentValue.toString(), ''); console.log(str); // 输出:"12345"
上述示例展示了对数组进行求和、查找最大值和连接字符串的操作,使用了自定义的 myReduce
方法实现。你可以根据具体需求在回调函数中实现不同的逻辑。
41、React中”栈调和”Stack Reconciler过程是怎样的?
在 React 中,栈调和(Stack Reconciler)是一种组件更新的核心机制。它负责比较新旧虚拟 DOM 树的差异,并将变化应用到真实的 DOM 上,从而实现组件的更新和渲染。
下面是栈调和的基本过程:
- 初始化阶段(Initial Mounting Phase):当一个组件首次渲染时,React 会创建组件的虚拟 DOM 树,即 React 元素树。然后,栈调和会遍历这棵树,将每个元素转换为相应的 DOM 节点,并插入到真实的 DOM 中。
- 更新阶段(Update Phase):当组件的状态或属性发生改变时,需要更新组件。React 会生成一个新的虚拟 DOM 树,并使用栈调和来比较新旧树之间的差异。
a. 比较阶段(Reconciliation):栈调和会逐层比较新旧虚拟 DOM 树中的节点。如果节点类型相同,React 会对其属性进行比较,以确定是否需要更新该节点。
b. 协调阶段(Commit):在比较阶段之后,栈调和会进行协调操作,将变更应用到真实的 DOM 上。这包括插入、更新、移动和删除 DOM 节点等操作。 - 卸载阶段(Unmounting Phase):当组件被卸载时,栈调和会将其对应的虚拟 DOM 树从真实的 DOM 中删除。
栈调和采用深度优先搜索的方式遍历虚拟 DOM 树,从而确定变更的精确位置。在比较阶段,React 使用一些策略来最小化 DOM 操作的数量,例如使用“key”属性进行节点复用、批量更新等。
需要注意的是,栈调和是 React 旧版的调和算法。在新版的 React 中,Fiber 架构引入了异步可中断的调和过程,即 Fiber Reconciler。它通过将调和过程分解为可中断的任务单元,实现了更高效的更新和渲染。但无论是栈调和还是 Fiber 调和,其基本原理都是比较新旧虚拟 DOM 树的差异,并将变更应用到真实的 DOM 上,从而使组件能够更新和渲染。
42、class组件和类组件区别?
- class组件是有状态的组件,可以定义state状态,函数组件无状态
- class组件有生命周期的,函数组件无生命周期
- class组件是有this对象,函数组件没有this对象
- 组件调用:class组件实例化后调用render方法调用,函数组件直接调用的。
- class组件内部的话,render方法return返回渲染jsx模板,函数组件直接返回即可
- ref获取子组件的对象,class组件可以直接获取到的,函数组件无法直接获取到。
- forwardRef((props,ref)=>{})
- 绑定bind改变this指向,只适用于class组件
43、useMemo和usecallback如何提升性能?
useMemo 主要缓存复杂运算的数据的结果,第二个参数,定义监听的变量,需要返回一个结果。
当父组件的组件更新的时候会导致子组件的重新渲染,但是如果父组件的更新的数据没有传递给子组件的话,这个时候如果还让子组件重新渲染的化,就会导致组件的更新的性能消耗比较大。
所以说这个时候我们可以使用useMemo, 或者React中内置的memo方法对子组件进行缓存,这样的话只有父组件更新跟子组件相关联的数据的时候才会导致子组件的重新渲染,从而提高组件的渲染性能。
但是如果我们给子组件传递方法的时候,上面memo方法的缓存就不起作用了,原因是父组件没更新一次会导致方法的重新调用,进而导致子组件的重新更新,所以这个时候我们可以使用useCallback对传递的方法进行缓存,监听数据更新后才会重新调用方法,从而提高组件的渲染性能。
44、React-router-dom V5和V6有什么区别?
1.V5中Switch换成Routes标签,
2.V5中exact属性没有了,V6默认是精准匹配
3.V5中的component属性,V6用的element,element属性是组件标签
4.V6中重定向导航组件换成Navigate
5.V6中路由嵌套直接采用组件包裹的方式,可以不适用path绝对路径,
6.V6中的 相当于vue中router-view
7.获取参数和Hooks方法的变化
8.props获取V6中props值没有参数数据,必须采用Hooks的路由获取数据。
9.withRouter在V6版本中也被去掉了。
45、React中Hooks及含义
useState
函数组件中设置state数据,参数默认值,返回的结果是一个数组,数组第一个值变量值,第二个值是一个函数,修改state的数据函数,相当于setstate方法
useEffect
相当于生命周期函数,第一个参数一个回调函数,第二参数是个数组【可以不填】
第一个参数返回一个函数的时候,相当于componentWillUnmount生命周期
第二个参数为空数组的时候,componentDidMount ,第二个如果不填的话,只要页面更新就会触发,componentDidUpdate生命周期
第二个参数的值可以监听某些数据的变化,把监听的变量放入第二个参数接口
useEffect是一个副作用函数,页面渲染完成后触发
useMemo useCallback 性能提升
父组件中引入子组件,传递变量的时候,父组件有任何数据的更新的话都会导致子组件的重新渲染,改变的数据跟子组件无关的话,导致性能问题,这个这个时候我们可以使用useMemo 或者memo对组件的数据进行缓存的操作
如果绑定的属性中有方法的话,这时候会发现数据改变的时候,memo会失效,这个使用useCallback对方法进行缓存,避免子组件的重新渲染,提升性能。
useRef
相当于类组件 createRef创建ref结点对象标识符的
let ref = useRef()
ref.current 获取当前的节点对象
useContext
useContext作用用来获取context状态树对象的内容
let context = useContext(对象的内容值)
useReducer
useReducer 是对useState数据的增强,useState,无法处理比较复杂逻辑,
可以把函数组件的数据进行一个集中式的管理,并且可以处理逻辑操作
参数是reducer的纯函数,返回的值是一个state数据,dispatch方法,第二参数可以是state的初始化的值