1. JSX绑定事件,this绑定有哪些规则?如何给函数传递参数?
-
this绑定规则
- 普通绑定 -
onClick={this.btnClick}
- 在内部是独立函数调用,所以this为undefined - this绑定方式一: bind绑定 -
onClick={this.btnClick.bind(this)}
- this绑定方式二: ES6 class fields -
onClick={this.btnClick}
-btnClick = () => {}
- this绑定方式三: 直接传入一个箭头函数 -
onClick={() => this.btnClick()}
- 普通绑定 -
-
给函数传递参
- event参数的传递 -
onClick={(event) => this.btn1Click(event)}
- 额外参数的传递 -
onClick={(event) => this.btn2Click(event, "http", 18)}
btn1Click(event) { console.log(event); } btn2Click(event, name, age) { console.log(event, name, age); }
- event参数的传递 -
2. JSX的代码是如何被编译为React代码的?它的本质是进行什么操作?
-
jsx是通过babel工具转换编译成React代码的
-
本质
- jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖
- 所有的jsx最终都会被转换成React.createElement的函数调用
`
3. 什么是虚拟DOM?虚拟DOM在React中起到什么作用?
-
什么是虚拟DOM?
- Virtual DOM 是一种编程概念,UI以一种理想化或者说虚拟化的方式保存在内存中
- Virtual DOM 本质上是 JavaScript 对象,是真实 DOM 的描述,⽤⼀个 JS 对象来描述⼀个 DOM 节点
- 我们知道jsx转成React代码的本质是 - 转换成React.createElement的函数调用
- 通过React.createElement的函数创建出来的
ReactElement
对象 - React利用
ReactElement
对象组成了一个JavaScript的对象树 - JavaScript的对象树就是虚拟DOM
-
虚拟DOM在React中的作用
-
虚拟DOM 通过diff算法 - 以最⼩的代价更新变化的视图
-
跨平台渲染
-
声明式编程 - 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式
- 你只需要告诉React希望让UI是什么状态
- React来确保DOM和这些状态是匹配的
- 不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来
-
4. 分析脚手架创建的React项目目录结构,并且删除文件后自己编写代码
-
public
- favicon.ico -- 应用程序顶部icon图标
- index.html -- 应用的index.html入口文件
- logo192.png -- 在manifest.json中被使用
- logo512.png -- 在manifest.json中被使用
- manifest.json -- 与web app配置相关
- robots.text -- 指定搜索引擎可以或者不可以爬取那些信息
-
src
- App.css -- App组件相关样式
- App.js -- App组件代码文件
- App.test.js -- App组件的测试代码文件
- index.css -- 全局样式文件
- index.js -- 整个应用程序的入口文件
- logo.svg -- 启动项目时,所看到的React图标
- reportWebVitals.js -- 默认帮我们写好的 注册pwa相关的代码
- setupTests.js -- 测试初始文件
-
pwa是什么? 全称Progressive Web App,即渐进式WEB应用
- 一个 PWA 应用首先是一个网页, 可以通过 Web 技术编写出一个网页应用
- 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能
- 这种Web存在的形式,我们也称之为是 Web App
- pwa可以将网页添加至主屏幕,点击主屏幕图标可以实现启动动画以及隐藏地址栏
- pwa实现离线缓存功能,即使用户手机没有网络,依然可以使用一些离线功能
- pwa实现了消息推送
5. React组件可以如何进行划分?分别有哪些不同的概念?
-
根据定义方式
- 函数组件
- 类组件
-
根据组件内部有无状态需要维护
- 无状态组件
- 有状态组件
-
根据组件的不同职责
- 展示型组件
- 容器型组件
-
关注UI的展示
- 函数组件,无状态组件,展示型组件
-
关注数据逻辑的展示
- 类组件,有状态组件,容器型组件
6. React重要的组件生命周期有哪些?分别列出他们的作用。
-
componentDidMount
- 会在组件挂载后立即调用
- 操作DOM
- 发送网络请求
-
componentDidUpdate
- 会在组件更新后立即调用
- 首次渲染不会执行此方法
- 可以在组件更新后操作DOM
-
conponentWillUnmount
- 会在组件卸载及销毁前调用
- 清除定时器
- 清除事件监听
7. React中如何实现组件间的通信?父传子?子传父?
-
父传子
- 在子组件通过属性传递内容
- 子组件通过props拿到内容
-
子传父
- 通过传递函数,调用回调函数
- 通过函数的参数传递要传递的数据
8. React中有插槽的概念吗?如果没有,如何实现插槽的效果呢?
-
没有插槽的概念
-
实现插槽效果的方式
- 直接在组件中插入children
- 在目标组件拿到插入的内容进行展示
-
通过props直接将要展示的内容传递给子组件
- 子组件通过props直接拿到要展示的内容
9. 非父子组件的通信有哪些方式?分别是什么作用?
-
事件总线
-
使用context
-
创建context
-
在要使用的组件,一般是根组件导入context
-
使用<context.Provider>包裹后代组件
-
在要使用的后代组件引入context
- xxxx.contextType = context
- 在render方法中可以通过this.context拿到传递过来的值
-
10. 非父子组件的通信有哪些方式?分别是什么作用?
-
Context
-
1.创建Context
-
2.使用<context.Provider>包裹后代组件
-
3.在要使用的后代xxxx组件引入context
- xxxx.contextType = context
- 在render方法中可以通过this.context拿到传递过来的值
-
-
事件总线EventBus
- Redux
11. 面试题:React的setState是同步的还是异步的?React18中是怎么样的?
-
在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
-
React的setState是异步的 -- 不要指望在调用
setState
之后,this.state
会立即映射为新的值 -
在react18之前, 在setTimeout,Promise等中操作setState, 是同步操作
-
在react18之后, 在setTimeout,Promise等中操作setState,是异步操作(批处理)
- 如果需要同步的处理怎么办呢? 需要执行特殊的
flushSync
操作
- 如果需要同步的处理怎么办呢? 需要执行特殊的
-
为什么要将setState设计成异步的
- 首先,若是将setState设计成同步的,在
componentDidMount
中请求多个网络请求时,会堵塞后面的网络请求
componentDidMount() { // 网络请求一 : this.setState // 网络请求二 : this.setState // 网络请求三 : this.setState // 如果this.setState设计成同步的,会堵塞后面的网络请求 }
-
一. setState设计为异步,可以显著的提升性能
- 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
- 最好的办法应该是获取到多个更新,之后进行批量更新
// 在一个函数中有多个setState时, this.setState({}) --> 先不会更新,而是会加入到队列(queue)中 (先进先出) this.setState({}) --> 也加入到队列中 this.setState({}) --> 也加入到队列中 // 这里的三个setState会被合并到队列中去 // 在源码内部是通过do...while从队列中取出依次执行的
-
二: 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步
- 首先,若是将setState设计成同步的,在
13. 性能优化:什么是SCU优化?类组件和函数组件分别如何进行SCU的优化?
-
shouldComponentUpdate -- SCU -- React提供给我们的声明周期方法
-
SCU优化就是 一种巧妙的技术,用来减少DOM操作次数,具体为当React元素没有更新时,不会去调用render()方法
-
可以通过
shouldComponentUpdate
来判断this.state
中的值是否改变shouldComponentUpdate(nextProps, nextState) { const {message, counter} = this.state if(message !== nextState.message || counter !== nextState.counter) { return true } return false }
-
React已经帮我们提供好SCU优化的操作
- 类组件: 将class继承自
PureComponent
- 函数组件: 使用一个高阶组件
memo
import {mome} from 'react' const HomeFunc = mome(function(props) { console.log("函数式组件") return ( <h4>函数式组件: {props.message}</h4> ) }) export default HomeFunc
- 类组件: 将class继承自
14. React为什么要强调不可变的力量?如何实现不可变的力量?
-
不可变的力量: 不要直接去修改this.state中的值(主要指对象),若是想修改的话,应该是将这整个值全部修改掉
- 注意: 值类型,在修改的时候,本身就全部替换掉了,所以不需要其他操作,直接改就可以
-
实现: 将对象浅拷贝赋值给一个新的变量,在将这个新的变量赋值给this.state中的值
15. React中获取DOM的方式有哪些?
-
ref获取DOM
getDOM() { // 方式一: 在react元素上绑定ref字符串 - 这种方式react已经不推荐了 // console.log(this.refs.http) // 方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素(推荐) // console.log(this.titleRef.current) // 方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入(16.3之前的版本) // console.log(this.titleEl) } <h3 ref="http">大大怪将军</h3> <h3 ref={this.titleRef}>小小怪下士</h3> <h3 ref={el => this.titleEl = el}>瑞克</h3> <button onClick={() => this.getDOM()}>获取DOM</button>
-
ref获取组件实例 --
createRef
import React, { PureComponent, createRef } from 'react' constructor() { super() this.state = {} this.HWRef = createRef() } getComponent() { console.log(this.HWRef.current) this.HWRef.current.test() } <HelloWorld ref={this.HWRef} /> <button onClick={() => this.getComponent()}>获取组件实例</button>
-
ref获取函数组件 -- 函数式组件是没有实例的,所以无法通过ref获取他们的实例 --
React.forwardRef
import React, { PureComponent, createRef, forwardRef } from 'react' const HelloWorld = forwardRef(function(props, ref) { return ( <div> <h2 ref={ref}>函数组件</h2> <h4>大大怪将军</h4> </div> ) }) constructor() { super() this.state = {} this.HWRef = createRef() } getComponent() { console.log(this.HWRef.current) } render() { return ( <div> <HelloWorld ref={this.HWRef} /> <button onClick={() => this.getComponent()}>获取DOM</button> </div> ) }
16.性能优化:React的diff算法和key的作用
-
React的渲染流程
- 在render函数中返回jsx, jsx会创建出
ReactElement
对象(通过React.createElement的函数创建出来的) ReactElement
最终会形成一颗树结构, 这颗树结构就是vDOM- React会根据这样的vDOM渲染出真实DOM
- 在render函数中返回jsx, jsx会创建出
-
React更新流程
- props/state发生改变
- render函数重新执行
- 产生新的DOM树结构
- 新旧DOM树 进行diff算法
- 计算出差异进行更新
- 最后更新到真实DOM
什么是diff算法? diff算法并非React独家首创,但是React针对diff算法做了自己的优化,使得时间复杂度优化成了O(n) 对比两颗树结构,然后帮助我们计算出vDOM中真正变化的部分,并只针对该部分进行实际的DOM操作,而非渲染整个页面,从而保证了每次操作后页面的高效渲染。
-
React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树
-
React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI
-
如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n³) ,其中 n 是树中元素的数量
-
如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围
-
这个开销太过昂贵了,于是,React对这个算法进行了优化,将其优化成了O(n)
-
同层节点之间相互比较,不会垮节点比较(一旦某个节点不同,那么包括其后代节点都会被替换)
-
不同类型的节点,产生不同的树结构(当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树)
-
开发中,可以通过key属性标识哪些子元素在不同的渲染中可能是不变的
-
-
在遍历列表时,总是会提示一个警告,让我们加入一个key属性,当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。
- 在最后位置插入数据 -- 这种情况,有无key意义并不大
- 在前面插入数据 -- 这种做法,在没有key的情况下,所有的li都需要进行修改
- 在中间插入元素 -- 新增2014, key为2016元素仅仅进行位移,不需要进行任何的修改
<ul> <li key="2015">Duke</li> <li key="2016">Villanova</li> </ul> <ul> <li key="2015">Duke</li> <li key="2014">Connecticut</li> <li key="2016">Villanova</li> </ul>
17. 什么是受控组件和非受控组件,如何使用?
-
受控组件
- 在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
- 我们将两者结合起来,使React的state成为“唯一数据源”
- 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作
- 被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”
this.state = { message: "" } changeInput(event) { console.log(event.target.value) this.setState({ message: event.target.value }) } render(){ <input type="text" value={message} onChange={(event) => this.changeInput(event)} /> }
-
非受控组件
- 在受控组件中,表单数据是由 React 组件来管理的
- 非受控组件中,表单数据将交由 DOM 节点来处理
this.messageRef.current.value // 在非受控组件中通常使用defaultValue来设置默认值 render(){ <input type="text" defaultValue={message} ref={this.messageRef} /> }
18. 什么是高阶组件?高阶组件在React开发中起到什么作用?
-
高阶函数: (满足一下调教之一) -- filter、map、reduce都是高阶函数
- 接受一个或多个函数作为输入
- 输出一个函数
-
高阶组件: Higher-Order Components,简称为 HOC
- 高阶组件是参数为组件,返回值为新组件的函数 -- 就是传入一个组件,对这个组件进行一些功能的增强,在返回出来新的组件
- 注意: 首先 高阶组件 本身不是一个组件,而是一个函数 其次,这个函数的参数是一个组件,返回值也是一个组件
- HOC 是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
-
高级组件应用的场景
- props的增强
- 利用高阶组件来共享Context
- 渲染判断鉴权
- 生命周期劫持
- ....
19. 什么是Fragment,有什么作用?
- Fragment 允许将子列表分组,而无需向 DOM 添加额外节点;
- 它的简写看起来像空标签 <> </>
- 如果我们需要在Fragment中添加key,那么就不能使用短语法
20. 什么是React的严格模式,在开发中有什么作用?
严格模式
- StrictMode 是一个用来突出显示应用程序中潜在问题的工具:
- 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI;
- 它为其后代元素触发额外的检查和警告;
严格模式作用
- 识别不安全的生命周期:
- 使用过时的ref API
- 检查意外的副作用 这个组件的constructor会被调用两次; 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用; 在生产环境中,是不会被调用两次的;
- 使用废弃的findDOMNode方法 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了,可以自行学习演练一下
- 检测过时的context API 早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的
21. React中如何实现过渡动画?常见的过渡动画组件有哪些?
- React社区为我们提供了react-transition-group用来完成过渡动画
常见的过渡动画组件
- Transition 在前端开发中,一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
- CSSTransition 在前端开发中,通常使用CSSTransition来完成过渡动画效果
- SwitchTransition 两个组件显示和隐藏切换时,使用该组件
- TransitionGroup 将多个动画组件包裹在其中,一般用于列表中元素的动画;
22. redux中如何进行异步的操作?和同步操作有什么区别?
-
通过中间件
- redux-thunk
- redux-saga
-
redux中的同步操作
- 执行了dispatch函数之后,对应的reducer函数收到action对象后立即得到执行,reducer执行完了之后,state立即就改变了,此时store.getState函数,取到的是最新的值
-
redux的异步操作
- 原则上redux并没有提供异步action的处理方案,异步action需要依赖第三方的中间件解决
- dispatch一个异步函数,目标state不会立即响应,而是要看异步函数内部的逻辑,来决定state什么时候响应
23. redux中如何进行reducer的拆分?拆分的原理和本质是什么?
-
主要利用模块化的思想,将不同的数据拆分到不同的模块
-
每一模块都有自己的目录结构
- reducer ---> 接受action对象,返回最新的state
- constants ---> 定义常量数据
- actoinCreator ---> 定义创建action对象的函数
- index ---> 导出reducer
-
store中的index文件
- 合并reducer,导出store实例
24. 什么是Redux Toolkit?核心API有哪些?并且说出他们的作用。
-
configureStore
-
包装createStore,同时提供简化的配置选项和良好的默认值
-
自动组合单独的slice reducer
-
可以添加任何中间件
- 默认包含redux-thunk,并启用Redux-DevTools调试工具
-
-
createSlice
- 接受一个具有render函数的对象
- 可以配置切片名称
- 初始状态值
- 自动 生成切片,并带有相应的actions
-
createAsyncThunk
- 接受一个动作类型字符和一个返回承诺的函数
- 生成一个pending/fulfilled/rejected基于该承诺分配动作类型的Thunk
25. 总结Redux的使用步骤,包括原始Redux用法和RTK用法。(重要)
-
原始的Redux的使用步骤
-
先从react-redux中导入Providre包裹根组件
-
将导出的store绑定到Provider组件的store属性中
-
创建store,目录结构
- actionCreator ---> 创建action对象
- constant ---> 定义常量数据
- reducer ---> 处理action对象,返回最新的state
- index ---> 人口文件,创建store,使用中间件
-
组件中的使用方式
- 定义函数 ---> mapStateToProps ---> 将store中的数据映射要组件的props中
- 定义函数 ---> mapDispatchToProps ---> 将dispatch的操作映射到props中
- 从react-redux中导入高阶组件对要导出的组件进行包裹,并把定义的函数传入connect函数
- 组件触发相应的事件,dispatch相应的对象,store中的数据改变,组件重新渲染
-
-
RTK用法
-
先从react-redux中导入Providre包裹根组件
-
将导出的store绑定到Provider组件的store属性中
-
创建store,目录结构:
-
index.js ---> 入口文件 ---> 创建和配置store ---> 主要是合并render
-
features ---> 要管理的数据模块
- 使用createSliceAPI创建一个slice对象
- name ---> 配置slice对象的名称
- initialState ---> 定义初始值
- reducer ---> 定义reduce函数的对象
- 导出slice对象的actions---> 组件中使用或者自己内部使用,
- 导出slice对象的reducer---> index文件合并reducer
-
-
26. React Router6的基本创建过程是什么?进行步骤总结。
-
安装react-router-dom -- npm install react-router-dom
-
选择路由模式 BrowserRouter使用history模式 / HashRouter使用hash模式
<HashRouter> <App /> </HashRouter>
-
通过Routes包裹所有的Route, 在其中匹配路由
-
Route用于路径的匹配
- path属性:用于设置匹配到的路径
- element属性:设置匹配到路径后,渲染的组件 -- Router5.x使用的是component属性
-
路由跳转 -- Link组件 / NavLink组件
- to属性 -- 用于设置跳转到的路径
-
路由重定向 -- Navigate
-
Not Found页面配置 -- path="*"
<Link to="/home">首页</Link> <Link to="/about">关于</Link> <Routes> <Route path="/" element={<Navigate to="/home" />} /> {/**Home页面*/} <Route path="/home" element={<Home />}> <Route path="/home" element={<Navigate to="/home/homebanner" />} /> <Route path="/home/homebanner" element={<HomeBanner />} /> <Route path="/home/homerecommend" element={<HomeRecommend />} /> </Route> <Route path="/about" element={<About />}></Route> <Route path="*" element={<NotFount />}></Route> </Routes>
27. React Router6的路由嵌套如何配置?Outlet的作用是什么?
// Home 页面
<Link to="/home/homebanner">轮播</Link>
<Link to="/home/homerecommend">推荐</Link>
<Outlet/>
- Outlet -- Outlet组件用于在父路由元素中作为子路由的占位元素, 类似于Vue中的 router-view
28. React Router6如何传递参数?如何在组件中获取参数?
-
路由参数传递有两种方式: 1. 动态路由的方式 2. search传递参数
-
动态路由
<Link to="/user/9978">用户</Link> <Route path="/user/:id" element={<User />}></Route>
// 获取动态路由参数 -- 需要通过 useParams 只能在函数式组件中使用 import { useParams } from "react-router-dom"; export function User() { const params = useParams() return ( <div> <h4>User Page</h4> <h4>id: {params.id}</h4> </div> ) }
-
search传递参数
<Link to="/user?name=大大怪将军&age=19"">用户</Link>
29. 类组件无法直接使用navigate、location等参数,应该如何进行操作?
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
function withRouter(WrapperComponent) {
return function(props){
// 1.导航
const navigate = useNavigate()
// 2.动态路由的参数: /detail/:id
const params = useParams()
// 3.查询字符串的参数: /user?name=why&age=18
const location = useLocation();
const [searchParams] = useSearchParams();
const query = Object.fromEntries(searchParams);
const router = {navigate, params, location, query}
return <WrapperComponent {...props} router={router} />
}
}
export default withRouter
import React, { PureComponent } from 'react'
import withRouter from "../hoc/with_router"
export class About extends PureComponent {
navigateTo(path) {
const { navigate } = this.props.router
navigate(path)
}
render() {
const { query } = this.props.router
return (
<div>
<h3>About Page</h3>
<h4>name: {query.name}-age: {query.age}</h4>
</div>
)
}
}
export default withRouter(About)
30. React Router6如何进行路由配置?如何配置路由的懒加载?
// 在单独的router/index.js文件中
// 路由懒加载/按需加载/异步加载 ?
// 这样暂时不会显示,因为是异步的要单独下载,需要加载一个loading动画React提供的Suspense组件
const Order = React.lazy(() => import("../page/Order"));
const User = React.lazy(() => import("../page/User"));
const router = [
{
path: '/',
element: <Navigate to="/home" />,
},
{
path: "/home",
element: <Home />,
children: []
}
]
export default router
<HashRouter>
<Suspense fallback={<h4>Loading~~~~</h4>}>
<App />
</Suspense>
</HashRouter>
31. 什么是Hooks?和传统的函数式组件有什么区别?和类组件有什么区别?(面试)
-
Hook指的类似于useState、useEffect这样的函数, Hooks是对这类函数的统称
-
首先需要了解函数式组件与类组件的优缺点
-
类组件可以定义自己的state,并且可以保存自己内部的状态
- 函数式组件不能定义自己的状态的, 因为函数每次调用都会产生新的临时变量
-
类组件有自己的生命周期 -- 而函数式组件没有
-
类组件在状态改变是会重新执行render函数
- 函数式组件时不会重新渲染的, 如果重新渲染, 整个函数会被重新执行, 相应的状态也会被重新赋值
-
同时类组件也有自己的缺点
- 随着业务的增多,类组件会变得越来越复杂
- 复用其中的状态也会很艰难,有时需要通过一些高阶组件
-
Hooks可以让我们在不编写class的情况下使用state以及其他的React特性
- Hook只能在函数组件中使用,不能在类组件
- 通过Hook可以在函数式组件中 定义自己的状态 完成类似于class组件中的生命周期功能
// 类组件实现计数器 import React, { PureComponent } from 'react' export class CounterClass extends PureComponent { constructor() { super() this.state = { counter: 100 } } subCounter() { this.setState({counter: this.state.counter - 1}) } addCounter() { this.setState({counter: this.state.counter + 1}) } render() { const { counter } = this.state return ( <div> 类组件实现计数器 <h2>counter: {counter}</h2> <button onClick={() => this.subCounter(1)}>-1</button> <button onClick={() => this.addCounter(1)}>+1</button> </div> ) } } export default CounterClass
// Hooks实现计数器 import React, { memo, useState } from 'react' const CounterHooks = memo(() => { let [counter, setCounter] = useState(100) return ( <div> Hooks <h2>counter: {counter}</h2> <button onClick={e => setCounter(counter - 1)}>-1</button> <button onClick={e => setCounter(counter + 1)}>+1</button> </div> ) }) export default CounterHooks