react16.8-17 & 全家桶学习 Q&A
笔记来源
开始 – React (Q1-2,有点傻的问题已经删掉了)
尚硅谷 React 教程(2022 加更,B 站超火 react 教程)_哔哩哔哩_bilibili (Q3-)
Q3:jsx 语法规则详细
A3:如下
定义虚拟 dom 时,不要写引号
标签中混入 js 表达式要用
{}
样式的类名指定不要用 class,用 className
内联样式,要用
style={{key:value}}
的形式去写只有一个根标签
标签必须闭合
标签首字母
- 若小写字母开头,则将该标签转为 html 中的同名元素,若 html 中无该标签对应的同名元素。则报错
- 若大写字母开头,react 就去渲染对应的组件,若组件未定义,则报错
Q4:react 创建虚拟 dom 的两种方式
A4:jsx 和 React.createElement()
Q5:真实 dom 与虚拟 dom 是什么与其的区别
是什么?virtual dom 本质是 js 对象形式对 dom 的描述,而页面渲染出的每一个节点则是一个 Real dom。
区别 1:虚拟 dom 不会进行排版和重绘操作,而真实 dom 会频繁重排与重绘。
区别 2:虚拟 dom 的总消耗是“虚拟 dom 增删改 + 真实 dom 差异增删改 + 排版与重绘”,真实 dom 的总消耗是“真实 dom 的完全增删改 + 排版与重绘”
本题题解:https://github.com/febobo/web-interview/issues/181
Q6:react 类组件中 this 的指向问题?构造函数/render 方法/自定义方法
构造函数一定指向类实例本身
render 方法是 react new 出实例后调用,因此 this 为类实例本身
类中自定义方法 this 指向,如下 changeWeather 中 this 为 undefined
1 | //A6案例 此时自定义函数中this指向存在问题 |
Q7:react 类组件自定义的方法中的 this 为 undefined,如何解决?两种解决方案如下
1 | //A7,1(第一种解决方案:强制绑定this:通过函数对象的bind()) |
Q8:什么是 state? 如何在类组件中使用 state?
A8,1 什么是 state?
state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)
组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
1 | //A8,2如何在类组件中使用state? |
Q9:精简类组件?干掉 constructor
1 | //A9 |
Q10
什么是 props?
在类组件中 props 的使用?
对 props 的一些限制?
props 简写方式?
类构造函数中 props 的使用?
函数组件中 props 的使用?
A10,1 什么是 props?
每个组件对象都有 props(properties 的简写)属性
组件标签所有属性都保存在 props 中
1 | //A10,2在类组件中props的使用? |
Q11:ref 的三种创建方法
string 绑定在标签
回调 Refs (内联与类的绑定函数)
React.createRef()
1 | //string绑定在标签 |
Q12:非受控组件与受控组件的理解?
非受控组件,一般是用 ref 实现,当用户提交表单时再去获取内容(现用现取节点值,不受我们监听 state 状态)
受控组件,一般用 onChange 来实现的,当用户改变 input 内容的时候自动调用 onChange 的回调然后去改变 state 中的状态(类似于 vue 中的双向绑定)
1 | //非受控组件案例 ,现用现取 |
Q13:什么是高阶函数与函数柯里化?在 react 中的使用场景?
高阶函数:如果一个函数符合下面 2 个规范中的任何一个,那该函数就是高阶函数。
若 A 函数,接收的参数是一个函数,那么 A 就可以称为高阶函数
若 A 函数,调用的返回值依然是一个函数,那么 A 就可以称之为高阶函数
常用的高阶函数有:Promise、setTimeout、数组的方法
函数柯里化:通过调用 继续返回函数 的方式,实现多次接受参数最后统一处理的函数编码方式
1 | //A13:函数柯里化,原生js案例 |
Q14:不用高阶函数和函数柯里化也能接受到监听事件中传入的 dataType 和 event(如下列代码所示)
1 | //A13 函数柯里化在react中的使用 |
Q15:旧生命周期钩子流程介绍?(React16.x 以及之前)
初始化阶段:由 ReactDOM.render()触发 –初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount() –>常用 (一般在这个钩子中做一些初始化的工作,例如:开启定时器、发送网络请求、订阅消息)
更新阶段:由组件内部 this.setState()或父组件 render 触发
- shouldComponentUpdate()
- componentWillUpdate()
- render() –>必须调用一个
卸载组件:由 ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() –>常用 (一般在这个钩子中做一些收尾的工作,例如:关闭定时器、取消订阅消息)
Q16:新生命钩子的流程介绍?(React17.x-至今)
初始化阶段:由 ReactDOM.render()触发 –初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount() –>常用 (一般在这个钩子中做一些初始化的工作,例如:开启定时器、发送网络请求、订阅消息)
更新阶段:由组件内部 this.setState()或父组件 render 触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render() –>必须调用一个
- getSnapshotBeforeUpdate
- componentDidUpdate()
卸载组件:由 ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
Q17
react/vue 中的 key 有什么作用?(key 的内部原理是什么)
为什么遍历列表时,key 最好不要用 index?
虚拟 DOM 中 key 的作用:
- 简单的说:key 是虚拟 DOM 对象的表示,在更新显示时 key 起着至关重要的作用
- 详细的说:当状态中的数据发生变化时,react 会根据【新数据】生成【新的虚拟 DOM】,随后 React 进行【新虚拟 DOM】和【旧虚拟 DOM】的 diff 比较,比较规则如下:
- 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
- 若虚拟 DOM 中内容没有变,则直接用之前的真实 DOM
- 若虚拟 DOM 中内容变了,则生成新的真实 DOM,替换掉页面之前的真实 DOM
- 旧虚拟 DOM 未找到与新虚拟 DOM 相同的 key,根据数据创建新的真实 DOM,随后渲染到页面
用 index 作为 key 可能会引发的问题:
- 若对数据进行:逆向添加,删除等破坏规则操作:会产生没有必要的真实 DOM 的更新 –> 虽然界面没问题,但效率低
- 如果结构中还包含输入类的 DOM:会产生错误 DOM 更新 –> 界面有问题
- 注意!如果不存在对数据的逆向添加、逆向删除等破坏顺序操作。仅用了渲染列表用于展示,使用 index 作为 key 是没有问题的
Q18:todoList 案例相关知识点
- 拆分组件、实现静态组件,注意 className、style 的写法
- 动态初始化列表、如何确定将数据放在哪个组件的 state 中?
- 某个组件使用:放在其自身的 state 中
- 某些组件使用:放在他们的共同的父组件 state 中(官方称为:状态提升)
- 关于父子之间通信:
- 【父组件】给【子组件】传递数据:通过 props 传递
- 【子组件】给【父组件】传递数据:通过 props 传递,要求父提前给子传递一个函数
- 注意 defaultChecked 和 checked 的区别,类似还有:defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
Q19:github 搜索案例涉及知识点
设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办
ES6 小知识点:解构赋值 + 重命名
1
2
3
4
5
6
7let obj = {a:{b:1}}
const {a} = obj
const {a:{b}} = obj
const {a:{b:value}} = obj消息订阅与发布机制
- 先订阅,再发布
- 适用于任何组件通信
- 要在组件中 componentWillUnmount 中取消订阅
fetch 发送请求(关注分离的设计思想)
Q20:前端路由基本原理
如果想在原生中自己封装一个路由库可以基于 history 封装好的基石(去 bootcdn 上搜索 history)
方法一:直接使用 h5 推出的 history 身上的 api
方法二:hash 值(瞄点)路径带#(兼容性强)
Q21:react 路由的基本使用
温馨提示:目前尚硅谷教的是 5 版本 npm i react-router-dom@5
1.明确好界面导航区和展示区
2.导航区的 a 标签改为 Link 标签
3.展示区些 Route 标签进行路径的匹配
1 | <Route path='/xxx' component={Demo}/> |
4.<App>
的最外侧包裹了一个<BrowserRouter>
或者<HashRouter>
Q22:路由组件与一般组件
写法不同
- 一般组件:
<Demo />
- 路由组件:
<Route path="/demo" component={Demo} />
- 一般组件:
存放位置不同
- 一般组件:component
- 路由组件:pages
接收的 props 不同
- 一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接受 3 个固定的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16history:
go: f go(n)
goBack:f goBack()
goForward:f goForward()
push:f push(path,state)
replace:f replace(path,sstate)
Location:
pathname:"/about"
search:""
state:undefined
match:
params:{}
path:"/about"
url:"/about"
Q23:NavLink 与封装 NavLink
NavLink 可以实现路由链接的高亮,通过 activeClassName 指定样式名
标签内容是一个特殊属性(children)
通过 this.props.children 可以获取标签体中的内容
Q24: react 解决样式丢失问题
- 在 public 下的 html 中解决
1 | <!--不要使用的方式--> |
- 使用 router 的 HashRouter 解决,因为#后的网络访问时认为是前端资源,因此不会将前端路由带上。
Q25:react 路由的严格匹配和模糊匹配
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
开启严格匹配
1 | <Route exact={true} path="/about" component={About}/> |
- 严格模式不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Q26:路由重定向的使用
1 | <Switch> |
Q27:嵌套路由
注册子路由时要写上父路由的 path 值
路由的匹配是按照注册路由的顺序进行的
Q28:向路由组件传递参数(使用优先级从上到下)
- params 参数
1 | <!--路由链接(携带参数)--> |
- search 参数
1 | <!--路由链接(携带参数)--> |
- state 参数(需要处理保密数据用)
1 | <!--路由链接(携带参数)--> |
Q29:编程式路由导航
借助 this.props.history 对象上的 api 对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.histroy.goForward()
this.props.history.go()
Q30:withRouter 的使用
withRouter 可以加工一般组件,让一般组件具备由组件所特有的 api
withRouter 的返回值是一个新组件
Q31:BrowserRouter 和 HashRouter 的区别
底层原理不一样
- BrowserRouter 使用的是 H5 的 history api,不兼容 ie9 及以下版本
- HashRouter 使用的是 url 的哈希值
url 表现形式不一样
- BrowserRouter 的路径中没有#,例如:localhost:3000/demo/test
- HashRouter 的路径中包含#,例如 localhost:3000/#/demo/test
刷新后对路由 state 参数的影响
- BrowserRouter 没有任何影响,因为 state 保存在 history 对象中
- HashRouter 刷新后会导致由 state 参数的丢失
备注:HashRouter 可以用于解决一些路径错误相关的问题
Q32:UI 组件库
国内使用多的 antd
官方文档:https://ant.design/docs/react/introduce-cn
主要掌握:按需引入和自定义主题
国外使用多 material ui
官方文档:https://material.io/
Q33:求和案例_redux 精简版
去除 Count 组件自身状态
src 下建立:
- -redux
- store.js
- count_reducer.js
- -redux
store.js
- 引入 redux 的 createStore 函数,创建一个 store
- createStore 调用时要传入一个为其服务的 reducer
- 暴露 store 给你的组件使用
count_reducer.js
- reducer 的本质是一个函数,接收:preState,action,返回加工后的状态
- reducer 有两个作用:初始化状态,加工状态
- reducer 被第一次调用时,是 store 自动触发的,传递的 preState 是 undefined
在 index.js 中检测 store 中状态的改变,一旦发生改变重新渲染
- 备注:redux 只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写
Q34:求和案例_redux 异步 action 版
明确:延迟的动作不想交给组件自身,想交给 action
何时需要异步 action:想要对状态进行操作,但是具体的数据靠异步任务返回。
具体编码:
- npm i redux-thunk,并配置在 store 中
- 创建 action 的函数不再返回一个一般对象,而是一个正在的函数,该函数中写异步任务
- 异步任务有结果后,分发一个同步的 action 去真正操作数据
备注:异步 action 不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步的 action
Q35:react-redux
使用目的是讲 react 组件和 redux api 分开
明确两个概念:
- UI 组件:不能使用任何的 redux 的 api,只负责页面的呈现和交互等
- 容器组件:负责和 redux 通信,将结果交给 UI 组件
如何创建一个容器组件 - 靠 react-redux 的 connect 函数
- connect(mapStateToProps.mapDispatchToProps)(组件)
- -mapStateToProps:映射状态,返回的是一个对象
- -mapDispatchToProps:映射操作状态的方法,返回值是一个对象
- connect(mapStateToProps.mapDispatchToProps)(组件)
备注:容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入
Q36:求和案例-react-redux 优化
容器组件和 UI 组件合并为一个文件
无需自己给容器组件传递 store,给
<App/>
包裹一个<Provider store={store}></Provider>
即可使用 react-redux 后不再需要自己去检测 redux 状态的变化,容器组件自动帮你完成这个工作
mapDispatchToProps 也可以简单的写成一个对象
一个组件要和 redux”打交道”要经过那几步?
定义好 UI 组件–不要暴露
引入 react-redux 的
connect
方法生成一个容器组件,并暴露,写法如下:1
2
3
4
5connect((state) => ({ count: state }), {
jia: createIncrementAction,
jian: createDecrementAction,
jiaAsync: createIncrementAsyncAction,
})(Count);在 UI 组件中通过
this.props.xxxx
读取和操作状态
Q37:求和案例-react_redux 数据共享版
定义一个 Person 组件,和 Count 组件通过 redux 共享数据
为 Person 组件编写:reducer、action,配置 constant 常量
重点:Person 的 reducer 和 Count 的 Reducer 要使用 combineReducer 进行合并,合并是重点
交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得要“取到位”
Q38:纯函数
一类特别的函数:只要是同样的输入(实参),必须得到同样的输出(返回)
必须遵从以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用
Date.now()
或者Math.random()
等不纯的方法
redux 的 reducer 函数必须是一个纯函数
Q39:求和案例_react_redux 开发者工具的使用
npm i redux-devtools-extension
.store 中进行配置
import { composeWithDevTools } from "redux-devtools-extension";
createStore(reducers,composeWithDevTools(applyMiddleware(thunk)));
Q40:求和案例_react_redux 最终版
所有变量名字要规范,尽量触发对象的简写形式
reducers 文件中,编写 index.js 专门用于汇总并暴露所有的 reducer
Q41:Hooks
- React Hook/Hooks 是什么?
1 | Hook是React 16.8.0版本新增的新特性/新语法 |
- 三个常用的 Hook
1 | State Hook:React.useState() |
- State Hook
1 | (1)State Hook让函数组件也可以有state状态,并进行状态数据的读写操作 |
- Effect Hook
1 | (1)Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子) |
- Ref Hook
1 | (1)Ref Hook可以在函数组件中存储/查找组件内的标签或任意其他数据 |
Q42:Fragment
使用
1 | <Fragment></Fragment> |
Q43:Context
理解:一种用于组件间通信的方式,常用于【祖组件】与【后代组件】间通信
1 | (1)创建Context容器对象 |
Q44:组件优化
Component 的两个问题
只用执行 setState(),即便不改变状态数据,组件也会重新 render() ==> 效率低
只要当前组件重新 render(),就会自动重新 render 子组件 ==> 效率低
效率高的做法
只有当前组件的 state 或者 props 发生改变的时候才重新 render()
原因
Component 中的 shouldComponentUpdate()总是返回 true
解决
1 | 办法1 |
Q45:render props
如何向组件内部动态传入带内容的结构(标签)?
1 | Vue中: |
children props
1 | <A> |
render props
1 | <A render={(data)=><C data={data}></C>}></A> |
Q46:错误边界
理解:用来捕获后代组件错误,渲染出备用页面
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件,定时器中产生的错误
使用方式:
getDerivedStateFromError 配合 componentDidCatch
1 | //生命周期函数,一旦后台组件出错,就会触发 |
Q47:组件通信方式
组件间的关系
父子组件
兄弟组件(非嵌套组件)
祖孙组件(跨级组件)
几种通信方式
1 | (1)props: |
比较好的搭配方式
1 | 父子组件:props |
React Router 6
前情提要
使用 vite Vite 官方中文文档 初始化项目:yarn create vite my-vue-app --template react
安装 react-router-dom 6:yarn add react-router-dom
概述
与 5.x 版本相比,改变了什么?
内置组件上:移除了 <Switch />
, 新增 <Routes />
等
语法变化:component={About}
变为 element={<About />}
等
新增多个 hook:useParams
,useNavigate
, useMatch
等
官方明确推荐函数式组件了!
…
Component
<Navigate />
:用于重定向,当这个组件被渲染会修改路劲和切换视图。
props:replace 用于控制跳转模式(push 或 replace,默认是 push)
<Routes> 和 <Route>
<Routes>
组件是 v6 版本用于替代移除的<Switch />
。
<Route>
和<Routes>
需要配合使用,且必须要用<Routes>
包裹<Route>
<Route>
相当于是 if 语句,如果其路劲与当前的 URL 匹配,则呈现对应的组件
<Route caseSensitive>
属性用于指定:匹配时是否区分大小写。默认为 false
<Route>
也可以嵌套使用,且可配合useRoutes()
配置“路由表”,但需要通过<Outlet/>
组件来渲染子路由。
完成一级路由的切换
实现:
用 BrowserRouter 将 App 包裹起来
引入 Link、Routes、Route 相关组件
写组件、引入组件
注册路由
1 | <Routes> |
- 默认重定向到 /Home:使用
<Route path="/" element={<Navigate to="/home" />} />
NavLink 自定义高亮效果
实现:
css 定义高亮效果样式
.active
将 className 写成一个函数,NavLink 切换时或应用初始化时会去调用并由
NavLink
组件给它传入一个{isActive:false}
的参数。通过传入
isActive
参数值切换样式.active
使用路由表生成路由
引入 useRoutes hook,将 路由规则 以 数组 的方式传递给 hook,让他生成页面的 routes 结构。
1 | // app.jsx |
1 | // app.jsx |
1 | // router/index.jsx |
嵌套路由
实现:
html,css 搭建嵌套路由的基本结构。一级路由对应页面中还有二级路由菜单、二级路由对应的页面。
给二级菜单套上 NavLink 或者 Link 供路由跳转到对应规则使用。
在路由表中新增二级路由规则,将匹配规则对应路由组件(页面组件)
路由携带 params 参数
需求 1:点击消息,在下方显示对应的 detail 内容。
实现如下:
先创建三级菜单对应的页面组件 Details
1 | // Details.jsx 显示 |
路由新增三级路由携带参数规则
1 | // ...二级路由配置 |
创建三级菜单 Link 链接并写 to 的携带参数的路径。(对应 “:” 插口写入数据即可)
1 | <Link to={`detail/${m.id}/${m.title}/${m.content}`}>{m.title}</Link> |
路由 search 参数
需求和需求 1 一样。
修改 Link 链接传递参数方式:类似为 query 参数。?id=1234&&title=kkkk
1 | <Link to={`detail?id=${m.id}&&title=${m.title}&&content=${m.content}`}>{m.title}</Link> |
修改三级路由参数规则,不需要占位
1 | // ...二级路由配置 |
修改 Details 组件,使用 useSearchParams hook 接受 search 参数,或使用 useLocation hook
1 | import React from 'react'; |
路由 state 参数
需求和需求 1 一样。
需求和需求 1 一样。
修改 Link 链接传递参数方式:类似为 query 参数。?id=1234&&title=kkkk
1 | <Link to="detail" state={{ id: m.id, title: m.title, content: m.content }}> |
修改三级路由参数规则,不需要占位
1 | // ...二级路由配置 |
修改 Details 组件,使用 useLocation hook 接受 state 参数
1 | import React from 'react'; |
编程式路由导航
在组件内使用 useNavigate 钩子,实现编程式路由导航
1 | const navigate = useNavigate() |
useInRouterContext()
作用:判断组件是否在路由上下文环境中。
返回值:true or false
useNavigationType()
作用:返回当前的导航类型(用户如何来到当前页面)
返回值:POP
PUSH
REPLACE
备注:POP
是指直接在浏览器打开这个路由组件(刷新页面)
useOutlet()
作用:用来呈现当前组件中渲染的嵌套路由
useResolvedPath()
作用:给定一个 URL 值,解析其中的:path、search、hash 值
完结撒花!🌹🌹🌹🌹
路漫漫..
要不要使用 useMemo 和 useCallback
哪些情况一个组件会重新渲染?
- 组件自己的 state 发生变化的时候
- 父组件传递过来的 props 发生变化的时候
- 父组件重新渲染了
useMemo 使用场景
如果一些值的计算量很大,那么可以用 useMemo 来做一个缓存,只有依赖变化时才会重新计算,而不是每次重新渲染都进行计算。
useCallback 使用场景
- 对于需要传递 “函数” 给子组件的场合,不使用 useCallback 的话,子组件每次都会重新渲染;
- 在调用节流、防抖函数时。
三个 Effect
注意:react18 后 useEffect 出现改变,与 useLayoutEffect 差距越来越小。
useInsertionEffect() / useLayoutEffect() / useEffect() 区别:执行时机不同
第一考虑用 useEffect ,当无法满足需求或出现 bug (如:修改原生 dom 样式时出现闪烁的情况使用 useLayoutEffect,动态添加标签性能出现问题使用 useInsertionEffect)
其他 hook
useDebugValue、useDeferredValue、useTransition、useId
文档:https://www.lilichao.com/index.php/2022/06/21/%e5%85%b3%e4%ba%8ehook/