项目完工

初始化项目

ts方式(此项目以ts运行)

1
create-react-app zhihu-daily --template typescript
  • 没有安装create-react-app的同学,请使用npx命令
1
npx create-react-app zhihu-daily --template typescript

js方式

  • 删除后面的typescript即可

Rem响应式处理

手动处理

  • 我们制作移动端网页的时候,需要考虑兼容性,比如我们UI给出的原型图是以iPhone5/6或者其他手机尺寸为参考的,这里就设置设计稿的宽度为375px,同时为了方便计算,我们设置1rem = 100px

  • 然后我们测量UI图的尺寸的时候,就**默认除以100,**这样子就得到了rem单位

  • 但是呢,每一人的手机不一定是375px宽度,我们在375宽度下设置了1rem = 100px,其他手机宽度的转换公式就如下

  • 最后就可以得到在不同手机上1rem应该等于多少px计算公式( 设备宽度 x 100 / 375 = ?px )

  • 知道了原理,书写下代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
*{
margin: 0;
padding: 0;
}
html{
/* 默认设置为100px */
font-size: 100px;
}
#abc{
width: 2rem;
height: 1rem;
font-size:0.18rem ;
background-color: rebeccapurple;
}
</style>
</head>
<body>
<div id="abc">
你好
</div>
<script>
(() => {
const computed = () => {
const html = document.documentElement;//获取html元素
const deviceWidth = html.clientWidth;//获取设备宽度
const designWidth = 375;//设计图的宽度
const ratio = deviceWidth * 100 / designWidth;

/* 这里你可以默认缩放,也可以设置超出设计图不进行扩展 */
// if(deviceWidth > designWidth) {
// html.style.fontSize = '100px';
// return;
// }
html.style.fontSize = ratio + 'px';
}
computed();
window.addEventListener('resize',computed);
})();
</script>

</body>
</html>

自动处理

  • postcss-pxtorem:将px转换为px
  • amfe-flexible:为html、body添加font-size,窗口调整时候重新设置font-size
  • 安装
1
2
npm install amfe-flexible -S
npm install postcss-pxtorem -D
  • 在主入口文件引入amfe-flexible
1
2
3
4
5
6
7
8
9
10
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import "amfe-flexible"
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
  • 配置postcss-pxtorem,可vue.config.js.postcssrc.jspostcss.config.js其中之一配置,权重从左到右降低,没有则新建文件,只需要设置其中一个即可:

  • 如果是react项目一开始没有eject,就需要安装下CRACO,这里就以这个为例子(好像还有react-app-rewired)

1
npm install  @craco/craco --save
  • 在项目根目录下创建配置文件craco.config.js,并根据实际情况完善配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
module.exports = {
style: {
postcss: {
mode: 'extends',
loaderOptions: {
postcssOptions: {
ident: 'postcss',
plugins: [
[
'postcss-pxtorem',
{
rootValue: 750/10, // (Number | Function) 表示根元素字体大小或根据input参数返回根元素字体大小
//unitPrecision: 5, // (数字)允许 REM 单位增长到的十进制数字
propList: ['*'], // 可以从 px 更改为 rem 的属性 使用通配符*启用所有属性
//selectorBlackList: [],// (数组)要忽略并保留为 px 的选择器。
//replace: true, // 替换包含 rems 的规则,而不是添加回退。
//mediaQuery: false, // 允许在媒体查询中转换 px
//minPixelValue: 0, // 最小的转化单位
exclude: /node_modules/i // 要忽略并保留为 px 的文件路径
}
]
],
},
},
},
},
};

  • 修改 package.json 中的 scripts
1
2
3
4
5
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
},
  • 最终可以看到进行了更改
1
2
3
4
5
6
7
8
9
10
11
.App {
width: 250px;
height: 100px;
background-color: red;
}
//自动转化为了
.App {
width: 3.33333rem;
height: 1.33333rem;
background-color: red
}

package.json列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
{
"name": "zhihu-daily",
"version": "0.1.0",
"private": true,
"dependencies": {
"@craco/craco": "^7.1.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"amfe-flexible": "^2.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"postcss-pxtorem": "^6.0.0"
}
}

参考

使用reduxjs/toolkit

  • 安装
1
yarn add reduxjs/toolkit react-redux
  • 使用起来也很方便,先抛弃一切redux的,这里只有切片,我们除了创建切片和一个主入口文件,其他什么都没有了

  • 创建切片

    • src\store/slice/base/index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { createSlice } from "@reduxjs/toolkit";

export const Info = {

}

export const Base = createSlice({
name:'base',
initialState:() => {
return Info;
},
reducers:{

}
})

export const BaseSliceAction = Base.actions;
export const BaseSliceReducer = Base.reducer;

  • 主入口文件
    • src\store/index.ts
1
2
3
4
5
6
7
8
9
10
import { configureStore } from "@reduxjs/toolkit";
import { BaseSliceReducer } from "@/store/slice/base";

const Store = configureStore({
reducer:{
base:BaseSliceReducer,
}
})

export default Store;
  • 传递各个组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Provider } from "react-redux";
import store from "@/store";

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<ConfigProvider locale={zhCN}>
<Provider store={store}>
<App />
</Provider>
</ConfigProvider>
);

  • 组件使用

    • 获取设置的state参数const { useSelector } from "react-redux"
    1
    2
    3
    4
    5
    6
    import {useSelector} from "react-redux"
    const selectProjectModalOpen = state => state.projectList.projectModalOpen;
    const showModal = useSelector(selectProjectModalOpen);


    //等同于 const showModal = useSelector((state) => state.projectList.projectModalOpen)
    • 调用设置的方法const { useDispatch } from "react-redux"
    1
    2
    3
    4
    const { useDispatch } from "react-redux";
    import {projectListSliceActions} from "../projectList/projectList.slice";
    const dispatch = useDispatch();//不需要传入任何参数,react-redux会自动去处理store
    <button onClick={() => dispatch(projectListSliceActions.closeProjectModal())}>点击我关闭</button>

元素隐藏/显示

详情页

可以利用useEffect来实现并发操作

1
2
3
4
5
6
7
8
9
10
11
12
13
useEffect(() => {
(async () => {
//获取详情图
const result = await api.queryNewsInfo(id ?? '');
console.log(result)
})()
},[])
useEffect(() => {
(async () => {
//获取点赞信息
const result = await api.queryStoryExtra(id ?? '');
})()
})

React渲染html字符串

1
2
3
4
5
6
7
function createMarkup() {
return {__html: 'First &middot; Second'};
}

function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}

创建的css样式放置到document.head当中

1
2
3
4
5
6
7
8
9
10
const handleStyle = () => {
const { css } = info;
if(!Array.isArray(css)) return;
const cssLink = css[0];//获取css链接
console.log(cssLink)
const linkDOM = document.createElement('link');
linkDOM.rel = 'stylesheet';
linkDOM.href = cssLink;
document.head.append(linkDOM);
}

使用flushSync

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React, { useState } from 'react';
import { flushSync } from 'react-dom';

const App: React.FC = () => {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
return (
<div
onClick={() => {
flushSync(() => {
setCount1(count => count + 1);
});
// 第一次更新
flushSync(() => {
setCount2(count => count + 1);
});
// 第二次更新
}}
>
<div>count1: {count1}</div>
<div>count2: {count2}</div>
</div>
);
};

export default App;
  • 需要注意的是,如果通过useEffect来,且依赖收集为一个空数组,那么就需要注意函数调用的state值的问题了

    • 初次渲染的时候,函数指向的是初始化时候的值,当有数据重新渲染的时候,如果不进行依赖收集去更新useEffect当中函数的指向,那么就会导致useEffect指向的永远是初始化时候的函数,从而导致函数内部的state值永远为初始化时候的值
    • 所以老师的解决办法如下
      • 也就是传入参数的方式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    //老师解决办法
    useEffect(() => {
    (async () => {
    //获取详情图
    const result = await api.queryNewsInfo(id ?? '');
    flushSync(() => {
    setInfo(result)
    handleStyle(result);
    })

    handleImage(result);
    })()
    },[])

    //下面这种是错误的,handleStyle和handleImage无法获取到最新的state值
    useEffect(() => {
    (async () => {
    //获取详情图
    const result = await api.queryNewsInfo(id ?? '');
    flushSync(() => {
    setInfo(result)
    handleStyle();
    })
    //保证DOM可以获取到
    handleImage();
    })()
    },[])


    //顺带一提,输出结果为 1,2,3,4
    useEffect(() => {
    (async () => {
    //获取详情图
    const result = await api.queryNewsInfo(id ?? '');
    console.log(1)
    flushSync(() => {
    console.log(2)
    setInfo(result)
    console.log(3)
    })
    console.log(4,info)
    handleStyle(result);
    //保证DOM可以获取到
    handleImage(result);
    })()
    },[])

设置图片

  • 图片设置
    • 为了更加好的体验,加入了onloadonerror事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 处理大图 */
const handleImage = (info:any) => {
const { image } = info;
if(!image) return;
const picDOM = document.querySelector<HTMLElement>('.img-place-holder');
const imgDOM = document.createElement('img');
imgDOM.src = image;
imgDOM.onload = () => {
//完成加载
imgDOM.style.cssText = 'width:100%'
//@ts-ignore;
picDOM.style.cssText = 'overflow:hidden';
picDOM?.appendChild(imgDOM);
}
imgDOM.onerror = () => {
//移除外层容器
const parent = picDOM?.parentElement;
parent?.removeChild(picDOM as any);
}
}

登录页面

  • reduxjs/toolkit

reduxjs/toolkit使用异步-方法1

  • 缺点是需要使用@ts-ignore,否者会报A computed property name must be of type 'string', 'number', 'symbol', or 'any'.警告

  • 异步函数

1
2
3
4
5
6
7
8
9
10
import {createAsyncThunk} from "@reduxjs/toolkit";

export const fetchUserDataAction = createAsyncThunk('fetch/fetchUserDataAction',() => {
console.log('执行了我')
//将作为payload值
return {
age:'18888'
}
})

  • store基本步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import {fetchUserDataAction} from "./actions";
import { createSlice} from "@reduxjs/toolkit";
//创建
export const Base = createSlice({
name:'base',
initialState:() => {
return Info;
},
reducers:{
userInfo: (state, action) => {
console.log(state,action)
state.other = {
name:'我是新名称'
}
}
},

extraReducers:{
//@ts-ignore
[fetchUserData.fulfilled](state,{payload}){
console.log('请看下图',action)
}
},
})



//主入口
import { configureStore } from "@reduxjs/toolkit";
import { BaseSliceReducer } from "@/store/slice/base";

const Store = configureStore({
reducer:{
base:BaseSliceReducer,
}
})
export default Store;


  • 使用
1
2
3
4
5
import {fetchUserDataAction} from "@/store/slice/base/actions";
import {useDispatch} from "react-redux";
const dispatch = useDispatch()
//调用异步
dispatch(fetchUserDataAction() as any);

执行输出

reduxjs/toolkit使用异步-方法2

  • 异步函数
1
2
3
4
5
6
7
8
9
import {createAsyncThunk} from "@reduxjs/toolkit";

export const fetchUserDataAction = createAsyncThunk('fetch/fetchUserDataAction',() => {
console.log('执行了我')
//将作为payload值
return {
age:'18888'
}
})
  • store基本步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import {fetchUserDataAction} from "./actions";
import { createSlice} from "@reduxjs/toolkit";
export const Base = createSlice({
name:'base',
initialState:() => {
return Info;
},
reducers:{
userInfo: (state, action) => {
console.log(state,action)
state.other = {
name:'我是新名称'
}
}
},
extraReducers(builder){
builder
.addCase(fetchUserDataAction.fulfilled,(state, action) => {
console.log('执行了我',action)
})
}
})


//主入口
import { configureStore } from "@reduxjs/toolkit";
import { BaseSliceReducer } from "@/store/slice/base";

const Store = configureStore({
reducer:{
base:BaseSliceReducer,
}
})
export default Store;
  • 使用
1
2
3
4
5
import {fetchUserDataAction} from "@/store/slice/base/actions";
import {useDispatch} from "react-redux";
const dispatch = useDispatch()
//调用异步
dispatch(fetchUserDataAction() as any);

执行输出

需要做的跳转处理

路由设置

  • 类似于Vued的路由前置守卫
  • 做法
    • 看下图

自己绘制说明

老师说明

  • 代码这里借助一个hooks来书写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
* 路由校验-判断是否需要登录 */
export const useCheckNeedAuth = (path:string) => {
const [,setRandomValue] = useState<string>('');
const { info }: any = useSelector<any>((state) => state.base);
const needAuth = !info && needAuthPath.includes(path)//登录信息不存在,并且访问的路径在需要认证的路由路径就需要认证;
const dispatch = useDispatch();
const navigate = useNavigate();
const location = useLocation();
useEffect(() => {
(async () => {
//不需要校验,直接返回
if(!needAuth) return;
//需要校验,请求获取用户信息(因为redux刷新后状态就没有了,需要重新请求)
const {payload:info} = await dispatch(fetchUserDataAction() as any).catch(() => {})
if(!info){
Toast.show({
icon:'fail',
content:'请先登录'
})
//console.log('执行跳转');
navigate({
pathname:'/login',
search:`redirect=${location.pathname}`,
})
return;
}
//console.log('获取到了信息')
//这里采用的方法就是更新一个值,从而去触发重新渲染,老师用的是时间戳,我这里就用uuid
setRandomValue(uuidv4());
})();
})
return [
needAuth,
]
}
  • 顺带一提,直接在useEffect当中使用async也是不被允许的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 错误写法
// useEffect(async () => {
// const result = await axios(
// 'https://hn.algolia.com/api/v1/search?query=redux',
// );

// setData(result.data);
// });

// 正确写法
useEffect(() => {
const fetchData = async () => {
const result = await axios(
'https://hn.algolia.com/api/v1/search?query=redux',
);

setData(result.data);
};

fetchData();
}, []);

收藏/取消收藏

  • 需要注意的是,如果我们想携带params参数和search参数,就需要自己组合了
    • 当然,你也可以使用window.location.href获取完整的路径信息,不过需要自己对http://localhost做处理

  • 所以我解决办法就是,使用组合
1
2
3
4
props.navigate({
pathname:'/login',
search:props.location.pathname + props.location.search,
})

收藏夹

  • 就添加了确认弹窗操作

实现组件的缓存

  • 缓存的方式
1
2
3
4
5
6
7
主流思想上:
1.不是标准的组件缓存,只是数据缓存A->B在A组件路由跳转的时候,把A组件中需要的数据「或者A组件的全部虚拟DOM」存储到redux中! !A组件释放,B组件加载! !当从B回到A的时候,A开始加载首先判断redux中是否存储了数据(或者虚拟DOM),如果没存储,就是第一次加载逻辑的处理; 如果存储了,则把存储的数据拿来渲染! !

2.修改路由的跳转机制,在路由跳转的时候,把指定的组件不销毁,只是控制display:none隐藏;后期从B回到A的时候,直接让A组件display:block! !

3.把A组件的真实DOM等信息,直接缓存起来;从B跳转回A的时候,直接把A之前缓存的信息拿出来用! !

  • 老师用的是一个老师的组件,这里就使用cjy0208大佬的react-activation(毕竟下载人数多嘛)
    • 好吧,此组件react18当中bug太多了………………并且需要使用老的ReactDOM.render写法,就不使用了
1
2
3
yarn add react-activation
# 或者
npm install react-activation
  • 跳过……………..

修改个人信息-文件上传

  • 一开始纠结文件上传失败后还显示图片,后面解决办法很简单
1
2
3
4
5
在uplod中上传失败之后抛出异常

throw new Error('上传失败,请重新上传')

然后在ImageUpload中加入属性 showFailed: false

遇到的问题

无法解析scss/sass提示create-react-app Cannot find module ‘sass’

  • 使用 create-react-app 的创建的项目,其默认的 webpack.config.js (这个文件默认隐藏,要查看需要运行 npm run eject,运行这个命令前需要本地 commit 代码)的文件中,可以看到是默认配置了 sass-loader 的选项的,所以在 react 项目中使用 sass 还是比较方便的。虽然默认配置了 sass-loader,但要使用 sass 还是需要先安装一下的,不然就会像我一样出现create-react-app Cannot find module 'sass'
1
2
3
npm install sass -D 
or
yarn add sass -D

无法解析less或提示Cannot find module ‘./index.module.less’ or its corresponding type declarations

  • 安装
1
2
3
npm install craco-less -D
or
yarn add craco-less -D
  • 编辑craco.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const CracoLessPlugin = require('craco-less');

module.exports = {

plugins: [
{
plugin: CracoLessPlugin,
options: {
// 此处根据 less-loader 版本的不同会有不同的配置,详见 less-loader 官方文档
lessLoaderOptions: {
lessOptions: {
modifyVars: {},
javascriptEnabled: true
}
}
}
}
]
};

  • 这样我们就可以使用下面命令来使用less了
1
import "./index.less"
  • 可能会出现下面问题Cannot find module './index.module.less' or its corresponding type declarations.

  • 找到src\react-app-env.d.ts,添加如下内容
1
2
3
4
declare module "*.less" {
const content: { [className: string]: string };
export default content;
}

配置别名

  • 更改craco.config.js
1
2
3
4
5
6
7
8
9
10
11
const path = require('path');
const resolve = dir => path.resolve(__dirname,dir);/* 计算路径 */
module.exports = {

webpack:{
alias:{
"@":resolve('src'),
}
}
};

  • 然后就可以使用了
1
2
//component:lazy(() => import("../views/Login")),
component:lazy(() => import("@/views/Login")),
  • 不过可能会出现Cannot find module '@/views/Login' or its corresponding type declarations.
    • 可以在 项目的根目录创建一个jsconfig.json或者tsconfig.json,添加如下内容即可
1
2
3
4
5
6
7
8
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/*": ["src/*"]
},
}
}
  • 更改完成记得重启服务,如果上述服务都没有用,可以试试看craco-alias(不过这个库已经被废弃了)

依赖收集导致无法获取到最新state值

  • 在做下拉加载组件的时候,下面的代码有问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { onBottom,options,style } = props;
const loadRef = useRef<any>();

useEffect(() => {
const loadRefCurrent = loadRef.current;
const observer = new IntersectionObserver((change) => {
const {isIntersecting} = change[0];
if(isIntersecting){
onBottom && onBottom();
}
},options ?? {});
observer.observe(loadRef.current);
return () => {
//移除监听
observer.unobserve(loadRefCurrent);
}
//这里有问题,依赖未写入
},[])
  • 导致父组件当中
1
2
3
4
5
/* 执行到底部的回调 */
const handleOnBottom = () => {
//子组件未添加依赖收集,导致newList永远是初始阶段的值
console.log(newList)
}
  • 所以需要添加依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const { onBottom,options,style } = props;
const loadRef = useRef<any>();

useEffect(() => {
const loadRefCurrent = loadRef.current;
const observer = new IntersectionObserver((change) => {
const {isIntersecting} = change[0];
if(isIntersecting){
onBottom && onBottom();
}
},options ?? {});
observer.observe(loadRef.current);
return () => {
//移除监听
observer.unobserve(loadRefCurrent);
}
//注意添加依赖
},[onBottom,options,style])

dispatch出现Argument of type ‘AsyncThunkAction{ age: string; }, void, AsyncThunkConfig>’ is not assignable to parameter of type ‘AnyAction’

  • 方法1:设置为any
1
2
3
4
5
6
import {fetchUserDataAction} from "@/store/slice/base/actions";
import {useDispatch} from "react-redux";

const dispatch = useDispatch()
dispatch(fetchUserDataAction() as any)

  • 方法2:暴露Store当中的dispatch类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { configureStore } from "@reduxjs/toolkit";


const Store = configureStore({
reducer:{

}
})
export type AppDispatch = typeof Store.dispatch
export default Store;


//使用
import {AppDispatch} from "@/store";
import {useDispatch} from "react-redux";
import {fetchUserDataAction} from "@/store/slice/base/actions";
const dispatch = useDispatch<AppDispatch>()
dispatch(fetchUserDataAction())

小知识点

stylesheet引入html文档的外部样式表

1
2
3
4
5
6
7
8
9
10
11
rel="styleSheet" 
打个比喻:就好比你带了个妞去一个party,虽然你知道这个妞是谁,但是你没给别人介绍啊,谁知道这个妞是干嘛的。
于是你加上rel="stylesheet",然后人们就知道了,哦......原来这个妞是你带来蹭饭的!

那么type="text/css" 也是一个道理,都是用来告诉浏览器的,我这个是一个css的文本,你要是不认识就别乱搞。

对于一些特殊浏览器 不能识别css的,会将代码认为text,从而不显示也不报错。

不过根据官方建议 ,一般还是加上比较好。

因为这个表示的是浏览器的解释方式,如果不定义的话,有些CSS效果浏览器解释得不一样。

React默认Event类型可以使用React.MouseEvent来指明

解构赋值省略掉部分参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 解构赋值省略掉部分参数
let obj = {
name:'法外狂徒',
age:18,
sex:'男',
qq:'15946875963',
phone:'15689784598'
}
// 利用解构得rest语法收集那些尚未被解构模式拾取的剩余可枚举属性键
/*
rest语法:剩余参数语法
官网:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters
如果函数(对象)的最后一个命名参数以...为前缀,则它将成为一个由剩余参数组成的真数组(对象)
*/
//remaining参数可以随意命名
let {name,...remaining} = obj;
console.log(remaining) //{ age: 18, sex: '男', qq: '15946875963', phone: '15689784598' }

才发现注释可以标明类型

  • 在非ts的情况下

标准React组件的类型

  • 可以使用React.ReactNode

老师正则的意思

  • 老师写了一个正则let reg = /\/api(\/[^/?#]+)/
  • 如果不考虑转义的问题和首尾固定的/ /这二个符号,这个正则就可以简写为下面这种
1
/api(/[^/?#])
  • 如果不考虑分组捕获,可以再简化
    • 含义: 匹配字符串当中具有/api内容的,并且获取后面内容不是?或者是#或者是/的字符内容
1
/api/[^/?#]
  • 图示

  • 分组捕获添加上去后老师的演示代码

less文件引入图片

  • 前提有别名~才可以这样子,否者要一层一层找下去
1
background-image: url("~@/assets/images/personBg.png");

自定义虚线

  • 示例1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//自定义虚线
&_dashed{
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 1px;
background-image: linear-gradient(
to right,
#ccc 0%,
#ccc 50%,
transparent 50%
);
//可以设置此值大小来改变间距
background-size: 14px 1Px;
background-repeat: repeat-x;
}
  • 示例2
1
2
3
4
5
6
7
8
9
background: linear-gradient(
to right,
transparent 0%,
transparent 50%,
#ccc 50%,
#ccc 100%
);
background-size: 50px 1px;
background-repeat: repeat-x;

指明传入props当中的回调类型提示: Type ‘Function’ is not assignable to type ‘MouseEventHandler<HTMLDivElement>‘.