知乎日报项目学习笔记

项目完工

  • 前端源码地址:https://github.com/superBiuBiuMan/-zhuhu_daily
  • 后端源码地址:https://github.com/superBiuBiuMan/zhihu-daily-admin
  • 学习地址:Bilibili
    • https://www.bilibili.com/video/BV1wx4y157Gu

初始化项目

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

create-react-app zhihu-daily --template typescript
  • 没有安装create-react-app的同学,请使用npx命令
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 )

  • 知道了原理,书写下代码
<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
  • 安装
npm install amfe-flexible -S
npm install postcss-pxtorem -D
  • 在主入口文件引入amfe-flexible
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)

npm install  @craco/craco --save
  • 在项目根目录下创建配置文件craco.config.js,并根据实际情况完善配置
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
"scripts": {
    
    
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
},
  • 最终可以看到进行了更改
.App {
    
    
  width: 250px;
  height: 100px;
  background-color: red;
}
//自动转化为了
.App {
    
    
    width: 3.33333rem;
    height: 1.33333rem;
    background-color: red
}

package.json列表

{
    
    
  "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

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

  • 创建切片

    • src\store/slice/base/index.ts
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
import {
    
     configureStore } from "@reduxjs/toolkit";
import {
    
     BaseSliceReducer } from "@/store/slice/base";

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

export default Store;
  • 传递各个组件
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"
    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"
    const {
          
           useDispatch } from "react-redux";
    import {
          
          projectListSliceActions} from "../projectList/projectList.slice";
    const dispatch = useDispatch();//不需要传入任何参数,react-redux会自动去处理store
    <button onClick={
          
          () => dispatch(projectListSliceActions.closeProjectModal())}>点击我关闭</button>
    

元素隐藏/显示

详情页

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

  useEffect(() => {
    
    
        (async () => {
    
    
            //获取详情图
            const result = await api.queryNewsInfo(id ?? '');
            console.log(result)
        })()
    },[])
    useEffect(() => {
    
    
        (async () => {
    
    
            //获取点赞信息
            const result = await api.queryStoryExtra(id ?? '');
        })()
    })

React渲染html字符串

function createMarkup() {
    
    
  return {
    
    __html: 'First &middot; Second'};
}

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

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

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

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值永远为初始化时候的值
    • 所以老师的解决办法如下
      • 也就是传入参数的方式
    //老师解决办法
    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事件
    /* 处理大图 */
    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'.警告

  • 异步函数

import {createAsyncThunk} from "@reduxjs/toolkit";

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

  • store基本步骤
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;


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

执行输出

reduxjs/toolkit使用异步-方法2

  • 异步函数
import {
    
    createAsyncThunk} from "@reduxjs/toolkit";

export const fetchUserDataAction = createAsyncThunk('fetch/fetchUserDataAction',() => {
    
    
    console.log('执行了我')
    //将作为payload值
    return {
    
    
        age:'18888'
    }
})
  • store基本步骤
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;
  • 使用
import {
    
    fetchUserDataAction} from "@/store/slice/base/actions";
import {
    
    useDispatch} from "react-redux";
const dispatch = useDispatch()
//调用异步
dispatch(fetchUserDataAction() as any);

执行输出

需要做的跳转处理

路由设置

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

自己绘制说明

老师说明

  • 代码这里借助一个hooks来书写
* 路由校验-判断是否需要登录 */
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也是不被允许的
// 错误写法
// 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做处理

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

收藏夹

  • 就添加了确认弹窗操作

实现组件的缓存

  • 缓存的方式
主流思想上:
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写法,就不使用了
yarn add react-activation
# 或者
npm install react-activation
  • 跳过…

修改个人信息-文件上传

  • 一开始纠结文件上传失败后还显示图片,后面解决办法很简单
在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'
npm install sass -D 
or
yarn add sass -D 

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

  • 安装
npm install craco-less -D
or
yarn add craco-less -D 
  • 编辑craco.config.js
const CracoLessPlugin = require('craco-less');

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

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

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

配置别名

  • 更改craco.config.js
const path = require('path');
const resolve = dir => path.resolve(__dirname,dir);/* 计算路径 */
module.exports = {
    
    
   
    webpack:{
    
    
        alias:{
    
    
            "@":resolve('src'),
        }
    }
};

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

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

  • 在做下拉加载组件的时候,下面的代码有问题
 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);
        }
        //这里有问题,依赖未写入
    },[])
  • 导致父组件当中
/* 执行到底部的回调 */
const handleOnBottom = () => {
	//子组件未添加依赖收集,导致newList永远是初始阶段的值
	console.log(newList)
}
  • 所以需要添加依赖
  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
import {
    
    fetchUserDataAction} from "@/store/slice/base/actions";
import {
    
    useDispatch} from "react-redux";

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

  • 方法2:暴露Store当中的dispatch类型
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文档的外部样式表

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

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

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

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

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

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

解构赋值省略掉部分参数

  • 笔记摘录:https://blog.csdn.net/qq_43379916/article/details/115632238
// 解构赋值省略掉部分参数
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(\/[^/?#]+)/
  • 如果不考虑转义的问题和首尾固定的/ / 这二个符号,这个正则就可以简写为下面这种
/api(/[^/?#])
  • 如果不考虑分组捕获,可以再简化
    • 含义: 匹配字符串当中具有/api内容的,并且获取后面内容不是?或者是#或者是/的字符内容
/api/[^/?#]
  • 图示

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

less文件引入图片

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

自定义虚线

  • 示例1
  //自定义虚线
  &_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
  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>’.

猜你喜欢

转载自blog.csdn.net/u014582342/article/details/129940735
今日推荐