遇到这样一个问题,初始化时用户登陆后需要获取到用户信息,但是发现获取用户信息这个接口触发了2次,这是不应该的,于是我查阅了一下资料,把自己的笔记记录下来。 还有就是使用mobx遇到的控制台警告问题,也一并记录一下。
一.React18,useEffect被调用了两次
-
import { useState, useEffect } from "react"; const Counter = () => { const [count, setCount] = useState(5); useEffect(() => { console.log("rendered"); console.log(count); }, [count]); console.log("rendered"); return ( <div> <h1> Counter </h1> <div> {count} </div> <button onClick={() => setCount(count + 1)}> click to increase </button> </div> ); }; export default Counter;
可以用这段话来解释:
当你在
strict mode in development
时,这是一个来自React18本身的问题。基本上,即使在React18中卸载之后,核心团队仍在试图改变组件的状态。useEffect
两次被调用与此功能有关。目前正在讨论如何解决这个问题。要了解更多信息,请观看YouTube视频。现在,您可以使用useRef
使用布尔值ref
来避免它,在您的例子中是这样的:
import { useState, useEffect, useRef } from "react";
const Counter = () => {
const firstRenderRef = useRef(true);
const [count, setCount] = useState(5);
useEffect(() => {
if(firstRenderRef.current){
firstRenderRef.current = false;
return;
}
console.log("rendered");
console.log(count);
}, [count]);
console.log("rendered");
return (
<div>
<h1> Counter </h1>
<div> {count} </div>
<button onClick={() => setCount( count + 1 )}> click to increase </button>
</div>
);
};
export default Counter;
其实还有一种做法就是不适用框架默认的严格模式,这样初始化数据的时候也只会走一次接口,我们看下react官网是怎么说的:
React 提供了 “严格模式”,在严格模式下开发时,它将会调用每个组件函数两次。通过重复调用组件函数,严格模式有助于找到违反这些规则的组件。
...
严格模式在生产环境下不生效,因此它不会降低应用程序的速度。如需引入严格模式,你可以用
<React.StrictMode>
包裹根组件。一些框架会默认这样做。
2.[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action..
报错源码展示:
import { makeAutoObservable, } from 'mobx'
import { getUserInfo } from '../api/login'
class UserStore {
userInfo = {}
constructor() {
makeAutoObservable(this)
}
getUserInfo = async ({ account }) => {
const res = await getUserInfo({ account })
if (res.code !== 'SUCCESS') return
console.log('用户个人信息res', res);
this.userInfo = res.data
// console.log('用户信息', this.userInfo);
}
}
export default UserStore
改进之后:
import { makeAutoObservable, runInAction } from 'mobx'
import { getUserInfo } from '../api/login'
class UserStore {
userInfo = {}
constructor() {
makeAutoObservable(this)
}
getUserInfo = async ({ account }) => {
const res = await getUserInfo({ account })
if (res.code !== 'SUCCESS') return
console.log('用户个人信息res', res);
runInAction(() => {
this.userInfo = res.data
})
// console.log('用户信息', this.userInfo);
}
}
export default UserStore
控制台报警告的原因其实是mobx中只能在acrion中重新赋值,异步导致赋值操作被加载到队列中,在action外面了,
runInAction 函数将赋值操作包裹在action内部.
3.关闭scss文件自动生成的css文件和min.css文件
每次创建.scss文件后,添加我们自己的样式,总是会多出两个不必要的css文件,看着挺讨人厌的,现在我们将其关闭一下。
(1)找到设置打开它
(2)在搜索框里搜索easysass,并点击在setting.json中编辑
(3)添加以下代码
"easysass.excludeRegex": "false",
"easysass.formats": [
{
"format": "expanded",
"extension": ""
},
{
"format": "compressed",
"extension": ""
}
],
这样就不会再生成css文件了。
4.执行完npm run eject项目报错
react报错jsx: Using `babel-preset-react-app` requires that you specify `NODE_ENV` or `BABEL_ENV` envi
查了一下,报错的原因是:eslint插件启动没有注入开发环境ENV,导致babel-preset-react-app无法解析
删除package.json中的eslintConfig配置
但是其实这样不能完美解决问题,推荐的做法是执行 npm run eject命令之前需要先提交一次我们的代码,因为执行这个操作时不可逆的,因为这个吃了点亏。
5.项目中建立全局样式
assets/styles/global.scss
建好全局样式文件后,引入到index.js中:
import './assets/styles/global.scss'//全局样式
验证一下全局样式有没有生效:
<label className='test'>label</label>
可以看到样式效果有了,也就是全局样式生效了,然后就可以自定义自己的全局样式文件了。
6.配置别名路径(用@代替src)
安装修改CRA配置的包:yarn add -D @craco/craco;
项目根目录下创建文件 craco.config.js (一定记得要和src同级,不要建错地方了)
craco.config.js中代码如下
const path = require("path")
module.exports = {
webpack:{
alias:{
"@":path.resolve(__dirname,"src")
}
}
}
package.json中修改部分:
//将 start/build/test 三个命令改为craco方式
"scripts": {
"start": "craco start",
"build": "craco build",
"test": "craco test",
"eject": "react-scripts eject"
},
然后重启项目,发现@配置src是生效了,但是@后面没有任何提示,一点不好用,还得自己手写,这显然不是我们想要的效果,再来配置一下。
@别名路径提示
在根目录下创建jsconfig.json配置文件(一定记得和src处于同级位置)
jsconfig.json添加如下代码:
{
"compilerOptions":{
"baseUrl":"./",
"paths":{
"@/*":["src/*"]
}
}
}
再重启项目,发现@后有了智能提示,有了我们想要的效果。
7.封装react高阶组件(实现路由权鉴)
其实也就是要实现vue的路由前置守卫的功能,但是react并没有给提供像vue的beforeEach这样的方法,所以需要自己封装高阶组件,实现登陆有token,跳转主页,没有token(未登录),不能访问(拦截在登陆页面),再就是有token但是想回到登录页,也要实现拦截,核心就是这样的思路。
新建一个AuthComponent.js文件,建议放在公共组件里面
import { getToken } from '@/utils'
import { Navigate } from 'react-router-dom'
const AuthComponent = ({ children }) => {
const isToken = getToken()
if (isToken) {
return <>{children}</>
}
else {
return <Navigate to='/login' replace></Navigate>
}
}
export {
AuthComponent
}
然后引入到项目得根文件App.js中,哪个页面需要登陆才能访问,就包裹哪个组件,以主页home为例:
<Route path='/' element={
<AuthComponent>
<Home />
</AuthComponent>
}>
</Route>
这样就实现了路由鉴权,也就是登陆这一模块的控制。
8.vscode自动格式化插件配置说明(Prettier - Code formatter)
安装Prettier插件,会解决保存后代码自动对齐的问题,之前配置的有点问题,导致每次保存代码后,还得手动格式化代码,非常麻烦,这下一并解决一下。
安装完之后然后添加以下配置代码:
// prettier相关设置
// 使能每一种语言默认格式化规则
"[html]": {
"editor.defaultFormatter": "vscode.html-language-features",
},
"[javascript]": {
"editor.defaultFormatter": "vscode.typescript-language-features",
},
"editor.formatOnSave": true, // 保存格式化
// html不换行
"js-beautify-html": {
// 换行
// "wrap_attributes": "force-aligned"
// 不换行
"wrap_attributes": "auto",
// "wrap_attributes": "aligned-multiple",
},
"prettier": {
"printWidth": 300, // 代码宽度 js超过 300换行
"bracketSameLine": true,
"trailingComma": "none", //禁止随时添加逗号,这个很重要。找了好久
"singleQuote": true, // 单引号不自动转换双引号
"semi": false, // 不添加分号
"proseWrap": "preserve", // 代码超出是否要换行 preserve保留
"arrowParens": "avoid", // 箭头函数一个参数不加括号
},
"files.associations": {
"*.html": "html"
},
"workbench.iconTheme": "vscode-icons",
"backgroundCover.autoStatus": true,
// 格式化vue文件
"files.autoSave": "onFocusChange",
"editor.fontSize": 14, // 设置字体
"editor.tabSize": 2, // 设置tab位2个空格,设置后在页面可查看.
"editor.tabCompletion": "on", // 用来在出现推荐值时,按下Tab键是否自动填入最佳推荐值
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.organizeImports": true // 这个属性能够在保存时,自动调整 import 语句相关顺序,能够让你的 import 语句按照字母顺序进行排列
},
"editor.lineNumbers": "on", // 设置代码行号
"editor.formatOnSave": true,
"terminal.integrated.shell.osx": "zsh",
"workbench.iconTheme": "vscode-icons",
"explorer.confirmDelete": false,
// #让vue中的js按"prettier"格式进行格式化
"vetur.format.defaultFormatter.html": "js-beautify-html",
"vetur.format.defaultFormatter.js":"prettier-eslint",
"vetur.format.defaultFormatterOptions": {
"js-beautify-html": {
// #vue组件中html代码格式化样式
"wrap_attributes": "force-aligned", //也可以设置为“auto”,效果会不一样
"wrap_line_length": 200,
"end_with_newline": false,
"semi": false, "singleQuote": true
},
"prettier": {
"semi": false,
"singleQuote": true,
"editor.tabSize": 2
},
"prettyhtml": {
"printWidth": 160,
"singleQuote": false,
"wrapAttributes": false,
"sortAttributes": false
}
},
// 设置编译器默认使用vetur方式格式化代码
"[vue]": {
"editor.defaultFormatter": "octref.vetur"
},