前端实习周记5
打开Drawer不刷新页面
原生antD组件中的Drawer应该是新建了一个z-index为最高值的遮罩层,在body下又生成一个独立于原本react渲染的标签进行渲染。根据需求,原本页面分为查询条件区域和查询结果区域,要求点开每一个单独的查询结果会出现一个Drawer,且这个Drawer要覆盖住查询结果和查询条件的区域,遂将Drawer中的getContainerof属性挂载到最外层包裹着查询条件和查询结果的组件里,但这又出现新的问题:
- 关闭Drawer后页面重新渲染,重新根据查询条件发送请求,不能像原来一样打开Drawer后仍然保持着搜索后的结果状态
想了很多解决办法,一开始并没有想到是因为Drawer挂载的地方覆盖了查询条件的区域,还想着利用redux存储查询的状态,按下Drawer关闭的按钮时调用这个状态;还想着使用usememo()缓存状态。
最后为了满足需求还是使用障眼法,将Drawer挂载到查询结果的组件内,调整高度和定位将其从表面上覆盖住查询条件的区域,虽然会出现不能响应式调整高度的问题,但考虑到项目本身是固定分辨率的,也算是解决了。
hooks缓存
react渲染页面是通过render把虚拟结点渲染到页面,通过对比fiber树,re-render重新渲染页面,为了更好的性能,我们一般提倡尽可能地减少react重新渲染页面的次数,这时候,usememo()和useCallback()就应运而生。
useMemo()
const cachedValue= useMemo(calculateValue,dependencies)
可以在react每次重新渲染的时候缓存计算的结果,其中:
- calculateValue: 缓存计算值的函数,且要为没有任何参数的纯函数,可以返回任意类型。react只会在首次渲染的时候调用该函数,之后如果dependencies没有发生变化时,react就直接返回相同的数值,避免了重复调用该函数;如果dependencies变化,才再调用该函数。
- dependencies: 依赖项,为calculateValue中所使用到的响应式变量组成的数组,包括props\state和所有直接在组件中定义的函数和变量。React 使用
Object.is将每个依赖项与其之前的值进行比较。
Object.is()
Object.is() 与 == 运算符并不等价。== 运算符在测试相等性之前,会对两个操作数进行类型转换(如果它们不是相同的类型),这可能会导致一些非预期的行为,例如 "" == false 的结果是 true,但是 Object.is() 不会对其操作数进行类型转换。
Object.is() 也不等价于 === 运算符。Object.is() 和 === 之间的唯一区别在于它们处理带符号的 0 和 NaN 值的时候。=== 运算符(和 == 运算符)将数值 -0 和 +0 视为相等,但是会将 NaN 视为彼此不相等。
注意:
和其他hooks一样,只能在组件的顶层和自定义hooks中使用,不能在循环或条件语句中调用。
用法
-
缓存组件,避免反复重新加载整个组件
function Todolist({todos,tab}){ const visibleTodos=filterTodos(todos,tab) }上面的组件需要利用filterTodos的方法过滤得到有效的todos,但由于利用到了props和state,所以每次更新都会重新运行filterTodos函数,因此,我们可以使用usememo进行改善:
function Todolist({todos,tab}){ const visibleTodos=useMemo(()=>filterTodos(todos,tab),[todos,tab]) } -
跳过组件的重新渲染
在默认情况下,当一个组件重新渲染时,react会递归地重新渲染它的所有子组件。依旧使用上述的例子,假设将visibleTodos传递给todoList的子组件:
function TodoList({todos,tab}){ const visibleTodos=filterTodos(todos,tab) //依然是建议使用useMemo() const visibleTodos=useMemo(()=>filterTodos(todos,tab),[todos,tab]) return( <div> <List items={visibleTodos} /> </div> ) }注意:对于一个直接依赖于在组件中创建的对象,需要对这个对象本身进行memo包裹
例如:这里的NotedTodos是在组件中创建的对象,当组件重新渲染时,组件中所有的代码都会再次运行,所以创建NotedTodos的代码也会在每次重新渲染时运行,而每次重新渲染的NotedTodos不一样,所以这里即便使用了useMemo, react也会重新渲染。
function TodoList({todos,tab,ischecked}){ const NotedTodos={noted:ischecked} const visibleTodos=useMemo(()=>{ return filterTodos(todos,tab,NotedTodos) },[todos,tab,NotedTodos]) }解决方式,使用memo包裹NotedTodos及使用了其作为依赖项的变量:
function TodoList({todos,tab,ischecked}){ const visibleTodos = useMemo(()=>{ const NotedTodos={noted:ischecked} return filterTodos(todos,tab,NotedTodos) },[todos,tab,ischecked])
useCallback( )
简单来说,可以看作是useMemo的一个语法糖,适用于缓存函数:
const visibleTodos=useMemo(()=>{
return ()=>{
todos.find(item=>item===tabs)
}
},[todos,tab])
}
//相当于
const visibleTodos=useCallback(()=>{
todos.find(item=>item===tabs)
} ,[todos,tab])
}
// 在 React 内部的简化实现
function useCallback(fn, dependencies) {
return useMemo(() => fn, dependencies);
}
- 二者区别:
useMemo缓存函数调用的结果。在这里,它缓存了调用computeRequirements(product)的结果。除非product发生改变,否则它将不会发生变化。这让你向下传递requirements时而无需不必要地重新渲染ShippingForm。必要时,React 将会调用传入的函数重新计算结果。(调用)useCallback缓存函数本身。不像useMemo,它不会调用你传入的函数。相反,它缓存此函数。从而除非productId或referrer发生改变,handleSubmit自己将不会发生改变。这让你向下传递handleSubmit函数而无需不必要地重新渲染ShippingForm。直至用户提交表单,你的代码都将不会运行。(创建)
使用场景
-
usememo返回函数
-
优化自定义hooks
纯函数
纯函数是函数式编程的一个重要概念,纯函数的主要特征:
- 只负责自己的任务,不会更改已存在的对象或变量,即不会改变函数作用域外的变量、不会改变在函数调用前创建对象
- 输入相同,输出则相同。给定相同的输入,总返回相同的结果。
针对第一点,我们进行局部mutation是可以的,比如我们可以在渲染时更改我们刚刚创建的变量和对象,例如:
function Todo(){
let list=[];
for(let i=1;i<10;i++){
list.push(<Item key={i} />)
}
}
这样的操作是可行的,但如果list变量是在Todo函数之外创建的话,这就违反了第一点的更改已存在的对象的要求。
副作用
副作用区别于纯函数,指的是不得不发生改变的情况,这些改变包括更新屏幕、启动动画、更改数据,在react中大多为事件处理程序如onClick。
事件处理程序
传递给事件处理函数的函数应该直接传递,而不是被调用,如:
//正确 ✔
<button onClick={handleClick}></button>
//错误 ❌
//这会使得handleClick函数在每次渲染时触发,即使不点击也会调用handleClick函数
<button onClick={handleClick()}></button>
同样的陷阱:
//正确 ✔
<button onClick={()=>alert('right')}
//错误 ❌
<button onClick={alert('wrong')}
调整图片拉伸
使用object-fit调整:和的内容适应到高度和宽度确定的框。
使用object-position切换:被替换元素的内容对象在元素框内的对齐方式。
redux原理
- store : 存放对象的状态的集合。唯一改变的store的方法是创建action,一个描述发生了什么的对象state,将其dispatch给store。
- state:描述发生了什么的对象state
- action: 描述行为,为reducer提供判断
- reducer: 纯函数,根据reducer中的逻辑,返回新的state来更新状态.
- (state, action) => newState
项目别名报错
报错信息:Failed to resolve import "./App" from "src\main.tsx". Does the file exist?
ts项目中导入文件名错误,排查原因后得出是在vite.config.ts中
resolve: {
...//没有添加tsx
extensions: ['.js', '.ts', '.json'] // 导入时想要省略的扩展名列表
},
修改后:
resolve: {
alias: {
'&': path.resolve(__dirname, './src') // 路径别名
},
extensions: ['.tsx', '.ts', '.js', '.json'] // 导入时想要省略的扩展名列表
}
引入png透明底文件失效
经检查是由于有样式覆盖该图片,导致其透明底色失效,解决方案:
background-color:unset
去除原有样式即可。
sass基础使用
变量
sass允许用变量$来存储重复使用的css数值,通常在scss文件顶部声明,例如:
$primary-color:#66ccff
body{
.container{
background-color:$primary-color
}
}
Mixins
可以让我们定义一组css规则,并且能够让我们在需要的地方重用这些规则,有些类似react当中我们自己封装的组件,用mixins写好之后,在调用的时候只需要传递相应的参数就可以显示,这有利于减少代码量,使得样式编写组件化,例如:
@mixin theme($theme: DarkGray) {
background: $theme;
box-shadow: 0 0 1px rgba($theme, .25);
color: #fff;
}
//这里定义了一组名为theme的样式,可以传入$theme变量
作为background和box-shadow的颜色
//直接利用@include引用theme,并根据需要修改$theme
.info {
@include theme;
}
.alert {
@include theme($theme: DarkRed);
}
.success {
@include theme($theme: DarkGreen);
}
文件模块化
在scss文件中,可以利用类似import语法如@use引入其他文件的变量,mixins和函数
// _base.scss
$primary-color:#66ccff
body{
.container{
background-color:$primary-color
}
}
//style,scss
@use 'base';
.filter{
color:base.$primary-color
}
继承
利用%为前缀编写变量,如果有样式@extend该变量,则继承并显示该变量的内容,需要注意的是,只有被@extend的%变量才会显示:
//message-shared被以下的类样式继承了
%message-shared {
border: 1px solid #ccc;
padding: 10px;
color: #333;
}
//equal-heights没有被继承
%equal-heights {
display: flex;
flex-wrap: wrap;
}
.message {
@extend %message-shared;
}
.success {
@extend %message-shared;
border-color: green;//继承后也可以额外添加样式
}
.error {
@extend %message-shared;
border-color: red;
}
.warning {
@extend %message-shared;
border-color: yellow;
}
计算
可以引用sass中的math,在scss文件中进行计算,例如
@use "sass:math"
//将px转换为百分比
.container{
width: math.div(600px * 960px) * 100%
}
我们也可以在一个scss文件中定义变量并计算,并在vite中配置全部scss文件,就可以使用里面的变量啦。