React18开发总结(完善中)

37 篇文章 1 订阅

这是是我在开发React项目随手记下的一些笔记,还在完善中,有好的想法大家一起交流。

1.React18,useEffect被调用了两次

  1. 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文件了。

但是呢,后来开发中又出现了类似的问题,重要编辑完样式保存时,VSCode会一直报警告:

我查了一下,原来是 EasyPass:无法生成CSS文件:没有为EasySass.formats[0]指定扩展名。 

比较粗暴的办法是加入下列配置代码:

"easysass.formats": [
     //** Easy Sass 插件 **/
        {
            "format": "compressed", // 压缩
            "extension": ".min.css"
        },
        {
            "format": "expanded",  // 未压缩  推荐使用
            "extension": ".css"
        }
    ],
    "easysass.targetDir": "",// 自定义css输出文件路径 当前目录下
"easysass.compileAfterSave": false,  //为false避免这类文件编辑保存后被编译无效

 再修改scss样式时,vsCode就再也不会发出类似的警告了。

总结,vsCode中配置的代码:

{
    "easysass.excludeRegex": "false",
"easysass.formats": [
     //** Easy Sass 插件 **/
        {
            "format": "compressed", // 压缩
            "extension": ".min.css"
        },
        {
            "format": "expanded",  // 未压缩  推荐使用
            "extension": ".css"
        }
    ],
    "easysass.targetDir": "",// 自定义css输出文件路径 当前目录下
"easysass.compileAfterSave": false,  //为false避免这类文件编辑保存后被编译无效
 // 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"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

}

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"
  },

额外给大家推荐几个不错的VSCode插件:

主题/外观美化区

Material Icon Theme:

它采用了 Google Material Design 风格,文件图标以及文件夹图标覆盖非常的全面,而且在暗黑模式下使用效果更佳。

Material Theme:

 

彩虹括号插件最新版VSCode已内置,无需安装,如需其他请使用Rainbow Brackets: 

 

 vscode的彩虹括号扩展。

Highlight Matching Tag:

 

ES6语法中的JavaScript代码片段:

  

9.在项目中按需加载svg(这里的svg指的是UI切好的svg)

在项目中使用svg比较常见,这里先说两种思路及解决方法,还需要完善。

首先需要配置svg的使用,需要在配置文件中修改,两种方式可以看到react的配置文件,执行npm run eject 可以看到配置文件,但是一旦执行,就不能回退了,不建议执行这个命令。

/node_modules/react-scripts/config这个文件夹目录下找到配置文件:

其实webpack中本身就有对svg的处理规则,并且file-loader还有一个默认的地址

把原来的关于svg的配置代码注释掉,加入下列代码:

这个注释掉的是自带的关于SVG的配置:

需要新添加的配置SVG的代码: 

{
              test: /\.svg$/,
              use: [
                {
                  loader: require.resolve('svg-sprite-loader'), 
                  options: {
                    //SymbolId:'icon-[name]'  
                  }
                }
              ]
},

后来我发现不能在这里修改webpack的配置, 如果这里加入了你的配置代码后,等你再安装一个第三方包后,重启项目,这里的配置代码就会被自动还原,所以这里修改webpack的配置是不可取的。(暂时还没想到怎么改进)

 然后安装这个包:

npm i svg-sprite-loader -D

在src目录下新建icons文件夹,在icons文件夹下新建svg文件夹,将自己的svg放进去,然后在icons文件夹下新建js文件,加入下列代码,先看目录结构:

index.js中代码:

import { useEffect, useMemo, useState } from 'react';

const Icon = ({ name, width, height }) => {

  const [svgModule, setSvgModule] = useState();

  const getSvg = async () => {
    const svg = await import(`./svg/${name}.svg`)
    setSvgModule(svg)
  }

  const iconName = useMemo(() => {
    if (svgModule && svgModule.default) {
      return `#${svgModule.default.id}`
    }
  //这块可能有警告
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [name, svgModule])

  useEffect(() => {
    getSvg()
  //这块可能也有警告
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <svg
      width={width}
      height={height}
      aria-hidden="true"
    >
      <use xlinkHref={iconName} />
    </svg>
  )
}

export default Icon

 

尤其注意这里的路径。 

在所需的页面将index引入:

import Icon from '@/icons/index';

使用:

<Icon name="comment" color="white" size={20} />

这个name字段就是你SVG的名称,可以自己定义,然后重启项目,发现SVG图标已经显示在页面了

但是还是有点问题,样式处理还是有些问题,还需再完善一下。 

这里推荐两篇文章,可以看一下:

在React中使用SVG和组件化

使用 svg-sprite-loader、svgo-loader 优化项目中的 Icon

我想修改SVG的尺寸和颜色,这个在平时开发中还是很常见的,我主要是参考了这篇文章:

react如何设置可自定义颜色的svg图标库(二)

我想要这样的效果,用户传递了颜色属性,SVG才会使用用户上传的颜色,否则就是默认颜色,这个比较贴合大多数的需求。

svg/index.js中代码:

import { useEffect, useMemo, useState } from 'react';

const Icon = ({ name, size, color }) => {
  // console.log(name, size, color)

  const [svgModule, setSvgModule] = useState();
  const [svgSize, setSvgSize] = useState({
    width: 30,
    height: 30
  });

  // 允许自定义颜色
  // const setColor = () => {
  //   let elem = document.getElementById(`${name}`)
  //   if (elem) {
  //     let children = document.getElementById(`${name}`).children
  //     for (let i = 0; i < children.length; i++) { // foreach报错
  //       children[i].style = `fill: ${color}`; // 这里不能用with语句,严格模式不支持with
  //     }
  //   }
  // }

  const setChildColor = (elem) => {
    const { children } = elem
    if (children) {
      for (let i = 0; i < children.length; i++) {
        children[i].style = `fill:${color}`
        if (children[i].children) {
          setChildColor(children[i])
        }
      }
    }
  }

  // 允许自定义颜色
  const setColor = () => {
    let elem = document.getElementById(`${name}`)
    if (elem) {
      setChildColor(elem)
    }
  }


  // 允许自定义尺寸
  const setSize = () => {
    if (!size) {
      setSvgSize({ width: 30, height: 30 })
      return
    }
    typeof size === "number" || "string" ?
      setSvgSize({ width: size, height: size }) :
      (size.length && size.length === 1 ?
        setSvgSize({ width: size[0], height: size[0] }) :
        setSvgSize({ width: size[0], height: size[1] })
      )
  }

  // 根据name拿到svg路径
  const getSvg = async () => {
    // console.log("getSvg")
    const svg = await import(`./svg/${name}.svg`)//注意这里的路径
    setSvgModule(svg)
  }
  const iconName = useMemo(() => {
    setColor() // 保证页面刷新时不会因为找不到id为name的标签而报错
    if (svgModule && svgModule.default) {
      return `#${svgModule.default.id}`
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [svgModule])

  useMemo(() => {
    setSize()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [size])

  useMemo(() => {
    setColor()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [color])


  useEffect(() => {
    getSvg()
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <svg
      {...svgSize}
      aria-hidden="true"
    >
      <use xlinkHref={iconName} />
    </svg>
  )
}

export default Icon

今天开发时安装了一个插件rc-tween-one 然后我发现SVG全都不能正常显示了,这个问题很怪异,为什么这样说呢,因为我刚执行完安装这个插件后,重启项目发现SVG不能显示了,应该不是这个插件的原因,具体原因我还没有找到。(其实这里的配置文件不能改,改掉后再安装别的插件,这里的配置代码会被还原,至于怎么改配置代码,我暂时还没弄明白)

我打印了一下这块的代码,发现是underfined。由于没有找到具体的原因,我把项目依赖删掉,然后重新安装依赖,又把svg重新配置了一下,才解决了问题。具体原因还有待分析。 

 然后还是在所需页面引入:

import Icon from '@/icons/index';

<Icon name="more" size="30" color="#6b41ff" />

 看下我引入的自己的SVG图标颜色有没有改变:

不传颜色:

<Icon name="pause" size="30" />

 

可以看到有效果,证明这一块的需求可以实现了。 

如果你想直接去除SVG的颜色,并且所有SVG的颜色均由用户重新设置,那么这样设置(不过这一步我并没有在代码中写,只是演示一下)。

安装svgo-loader

在配置文件 webpack.config.js 里加入自动消除掉 fill ,具体代码如下:

{
     test: /\.svg$/,
     use: [
      { loader: 'svg-sprite-loader', options: {} },
      { loader: 'svgo-loader', options: {
         plugins:[
         // 加载时删除svg默认fill填充色
          {removeAttrs:{attrs: 'fill'}}
         ]
      }}
     ]
    },

通过css设置currentColor

只需要新建一个样式文件(比如 icon.less ),写入下面的css;再将这个样式文件引入之前写好的通用组件 icon.js 。在 icon.js 中,为 svg 标签加上 color 属性就可以了。

g[fill] {
    fill: currentColor;
    fill-opacity: 1;
}
g[stroke] {
    stroke: currentColor;
    stroke-opacity: 1;
}
path[fill] {
    fill: currentColor;
    fill-opacity: 1;
}
path[stroke] {
    stroke: currentColor;
    stroke-opacity: 1;
}

原理很简单,本质上就是把svg文件中的 g 标签或者 path 标签中控制颜色的属性给改了。

10.设置项目代理

在src目录下新建setupProxy.js

const { createProxyMiddleware } = require("http-proxy-middleware");
 
module.exports = function (app) {
  app.use(
    "/dev_api",//开发环境设置的环境变量名称
    createProxyMiddleware({
      target: "XXX",//代理地址
      changeOrigin: true,
      pathRewrite: {
        "^/dev_api": "",
      },
    })
  );
};

与src同级新建.env.development

REACT_APP_API_URL =  '/dev_api'

11.使用antd的card组件遇到报错警告

先看一下警告:

其实这个就是说<p>标签不能作为<div>标签的父标签 

再看看一下代码:

确实<p>标签作为父标签了,那去掉就行了。 其实可以举一反三,咱们使用<card>时,大部分是自己自定义内容,其实可以不使用<card>自带的<p>标签,不过<p>标签比<div>语意感要好。

12.使用useEffect初始化获取数据导致栈溢出

其实就是调用接口的时候陷入了死循环,因为useEffect不仅在组件didMounts的时候被触发了,还在didUpdate的时候被触发了。在useEffect中获取数据后,通过setDate改变state,触发组件渲染更新,从而又进入到了useEffect中,无限循环下去。所以,这种情况下,我们必须要给useEffect方法的第二个参数传入一个空[],以使得useEffect中的逻辑只在组件didMounts的时候被执行。
参考地址:useEffect使用(不能直接在useEffect中使用async函数

面试官:如何解决React useEffect钩子带来的无限循环问题

错误例子:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
  const [data, setData] = useState({ hits: [] });
 
  useEffect(async () => {
    const result = await axios(
      'http://localhost/api/v1/search?query=redux',
    );
 
    setData(result.data);
  }, []);
 
  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

正确例子:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
 
function App() {
  const [data, setData] = useState({ hits: [] });
 
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'http://localhost/api/v1/search?query=redux',
      );
 
      setData(result.data);
    };
 
    fetchData();
  }, []);
 
  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}
 
export default App;

后来看了一下,终于发现了问题,原来是我接口和方法名重名导致的栈溢出:

只要改一下方法名,问题就解决了。 

12.react中引入图片不显示

如果是这样引入图片的话, 那么页面上图片显示。两种解决方案:

(1)import 引入

(2)通过 require 加 default

import React from 'react'
export default function App () {
  return (
    <div className="box">
      <img src={require('./xxx').default} alt=""/>
    </div>
  )
}

参考地址:React 引入img 图片不显示 

13.使用css module

为什么要使用css module呢?其实是为了解决样式冲突的问题.在vue中样式可以通过scoped属性,从而限制样式生效范围

React项目是支持css module的, 不过需要开启一下这个功能,还是老规矩,找到项目得配置文件:

/node_modules/react-scripts/config这个文件夹目录下找到配置文件:

只需要添加一句代码就可以,注释的代码时框架自带的。

看下项目中如何使用:

import styles from './index.scss';
 {/* <p className='history_title'>History</p> */}

 //开启css module的写法
 <p className={styles.history_title}>History</p>

14.用分页器控制数据,控制台出现警告

先看一下代码:

  const [params, setParams] = useState({
        page: 1,
        pageSize: 8,
    })
    const [current, setCurrent] = useState(1);
    //分页改变
    const onChange = (page) => {
        setParams({
            ...params,
            page
        })
        console.log(page);
        setCurrent(page);
    };
<Pagination current={current} onChange={onChange} total={50} />

检查了一下数据没啥问题,暂时没能消除这个警告。

15.路由跳转的试试报了一个警告

问题是这样,登陆后是home页面,但是home页面有自己的子组件,从子组件跳转到别的页面后,就再也不能跳转回来,目前不知道该怎么解决这个问题,先记录一下。 

我觉得应该是我路由守卫那块的逻辑出了问题,目前只是推测。

这个其实应该是hash路由模式中使用了history模式,但我觉得我路由这块没有啥错误。

参考了这篇文章,但是没有解决我的问题,我再研究一下。

如何优雅的使用react router v6, 并实现全局守卫

react-router-dom v6+ts实现路由守卫

把路由走了好几遍,发现不是路由的问题,是我跳转传参写法错误导致的:

错误写法:

 我的id并没有给事件传过去,所以导致了错误,改一下就好了:

16.Hook 版dailog

所封装的弹窗组件dialog.js

import React, { useState, useImperativeHandle } from 'react'
import PropTypes from 'prop-types'
import { Modal } from 'antd';

let ok = () => {};
const DialogCom = ({btnTxt = ['取消', '确定'], children, cRef, autoClose = true, ...reset}) => {
  const [visible, setVisible] = useState(false);

  const open = cb => {
    setVisible(true);
    ok = cb;
  }

  useImperativeHandle(cRef, () => ({
    open: cb => open(cb),
  }));

  const handleCancel = () => {
    setVisible(false);
  }

  const handleOk = () => {
    autoClose && setVisible(false);
    ok(handleCancel);
  }

  return <Modal
    {...reset}
    maskClosable={false}
    visible={visible}
    onOk={handleOk}
    onCancel={handleCancel}
    okText={btnTxt[1]}
    cancelText={btnTxt[0]}
  >
    {children}
  </Modal>
}

DialogCom.propTypes = {
  btnTxt: PropTypes.array,
  children: PropTypes.any.isRequired,
  cRef: PropTypes.object.isRequired,
  autoClose: PropTypes.bool,
}

export default DialogCom

使用方法:

import React, { useRef } from 'react'
import { Button } from 'antd';
import Dialog from './dialog'

const DialogDemo = () => {
  const childRef = useRef();

  const open = () => {
    childRef.current.open(cancel => {
      // cancel();
      console.log('打开')
    });
  }

  const resetForm = () => {
    console.log('重置表单')
  }

  const config = {
    title: '提示',
    btnTxt: ['关闭', '提交'],
    centered: true,
    width: '400px',
    afterClose: resetForm, // Modal完全关闭后的回调
  }

  return <>
    <Button type="primary" onClick={open}>打开弹窗</Button>
    <Dialog {...config} cRef={childRef}>
      <p>我是弹窗</p>
      <p>我是弹窗</p>
    </Dialog>
  </>
}

export default DialogDemo

如果您是使用vue开发的话,请移步这两篇文章:

封装Vue Element的dialog弹窗组件

封装Vue Element的form表单组件

参考地址:hook 版dailog

开发的时候可以稍微参考一下这篇文章:

React 项目 极客园

17.React获取antd的Input组件值的两种方法 

1.使用event.target.value

import React from 'react';
import { Input } from 'antd';


 const [inputValue, setInputValue] = useState('');
 const handleInput = (e) => {
    console.log('e', e.target.value);
    setInputValue(e.target.value)
  }


const handleAdd=()=>{
  //省略代码
descript: inputValue,
}
   
 
return(
       <Input placeholder="Add a comment" onChange={handleInput} />
)

}

2.使用ref勾子

直接用ref.current.value出错是因为这不是基础的html组件,Input组件是在基础输入框input上的一个封装,要找到Input组件里面的基础input组件,才有value值。

import React ,{useRef} from 'react';
import { Input } from 'antd';

 const input2 = useRef(null)
  const handleInput = (e) => {
    // console.log('e', e.target.value);

    const val = input2.current.input.value
    console.log('val', val);
    // setInputValue
  }
 
return(
     <Input placeholder="Add a comment" ref={input2} onChange={handleInput}
)

}

因为Input组件把输入框的值也存放到,我试着用input2.current.state.value去取,但是总是取上一次的结果,这说明了在onChange调用change2 函数的时候,组件还没有开始setState,如果是写非受控组件的话也是可以这么用的,因为已经渲染完成了。

局限性:只适用于函数式组件(非受控组件)

参考地址:React获取ANTD的Input组件值的两种方法

18.实现和vue一样的深度选择器效果

以我使用的antd的card组件为例,原本我的样式是这样的:

 从控制台也能看出这个标题的样式名:

但是加了样式后保存刷新,样式立马失效,这时想到了vue的深度选择器/deep/,在react中怎么使用呢?

 其实只要找到这个标签的父级样式,从自己的样式表看或者从控制台看,这样写:

.side_card_comments {
  border-radius: 20px;
  padding-left: 10px;
//关键代码
  :global(.ant-card-head-title) {
    font-size: 18px;
    margin-left: -238px;
    font-family: Arial Bold;
  }
}

这样就修改了样式。 

Vue和React的样式穿透 —— 深度选择器

19.根据后端返回数据的区间范围显示不同的颜色

这个是颜色区间范围值。

实现的代码逻辑:

    function handleColor(num: any) {
      let color = ''
      if (num >= 0 && num < 25) {
        color = '#FA3C71'
      } else if (num >= 25 && num < 50) {
        color = '#FF6D96'
      } else if (num >= 50 && num < 75) {
        color = '#FFA2B2'
      } else if (num >= 75 && num < 100) {
        color = '#FFBFE7'
      } else if (num >= 100 && num < 125) {
        color = '#CDC8F3'
      } else if (num >= 125 && num < 150) {
        color = '#5D80F8'
      } else if (num >= 150 && num < 175) {
        color = '#5D80F8'
      } else if (num >= 175 && num < 500) {
        color = '#063462'
      }
      return color
    }

 代用这个函数的时候,要传入去掉百分比的数值:

    function getColor(node: any): any {
      // return colorData[getRandomInt([0,6])].color;
      console.log('点击的当前颜色', node)
      console.log('点击的当前颜色select', node.select)
      console.log('node.pct', parseInt(node.pct))

      // return node.select ? node.color: '#adb3cb';

      return node.select ? handleColor(parseInt(node.pct)) : '#adb3cb'
      // return node.select ? node.color: '#adb3cb';
    }

这个方法还需要再优化,我之前是这么写的,没有实现效果:

      switch (compound) {
        case 'compound >0 && compound<25':
          return ' #FA3C71'
        case 'compound >25 && compound<50':
          return '#FF6D96'
        case 'num >= 50 && num < 75':
          return '#FFA2B2'
        case 'num >= 75 && num < 100':
          return '#FFBFE7'
        case 'num >= 100 && num < 125':
          return '#CDC8F3'
        case 'num >= 125 && num < 150':
          return '#8AA3FF'
        case 'num >= 150 && num < 175':
          return '#5D80F8'
        case 'num >= 175 && num <500':
          return '#063462'
        default:
          return
      }
    }

20.echarts 属性详解

title

图表的标题,包括主标题和副标题

text: 主标题文本内容,字符串类型。
text: 主标题文本内容,字符串类型。
subtext: 副标题文本内容,字符串类型。
left: 主副标题水平位置,可以设置为像素值、百分比,或者预设的字符串值(例如’left’, ‘center’, ‘right’)。
top: 主副标题垂直位置,同样可以设置为像素值、百分比或预设的字符串值。
textAlign: 主副标题文本水平对齐方式,可选值为’left’, ‘center’, ‘right’。
textStyle: 主副标题文本样式,是一个 JavaScript 对象,可以设置字体大小、颜色、字体粗细等样式属性。

tooltip

提示框组件,用于显示数据项的详细信息,可以自定义显示内容和样式。

trigger: 提示框触发类型,可选值有’item’(数据项触发),‘axis’(坐标轴触发),‘none’(不触发)。也可以通过设置回调函数来自定义触发方式。
axisPointer: 坐标轴指示器的相关配置,包括类型、线条样式、标签等。
formatter: 提示框的内容格式化函数,可以通过函数参数的方式获取到提示框显示的数据,从而进行格式化处理。
backgroundColor: 提示框的背景色,可以设置为颜色值或渐变对象。
padding: 提示框的内边距,是一个数组,分别表示上、右、下、左四个方向的内边距大小。

legend

图例组件,用于展示数据系列的标识和名称,支持点击切换系列的显示状态。

data: 图例的数据,是一个数组,每个元素对应一个系列的名称。
type: 图例的类型,可选值为’plain’(普通图例)、‘scroll’(可滚动图例)。
orient: 图例的布局方式,可选值为’horizontal’(水平布局)、‘vertical’(垂直布局)。
selectedMode: 图例的选中模式,可选值为’multiple’(多选)和’single’(单选)。
left: 图例组件的水平位置,可以设置为像素值、百分比或预设的字符串值。
top: 图例组件的垂直位置,同样可以设置为像素值、百分比或预设的字符串值。
itemWidth: 图例标记的宽度,可以设置为像素值。
itemHeight: 图例标记的高度,可以设置为像素值。

grid

直角坐标系内绘图网格,可以设置位置、大小和样式等属性。

left: 网格组件左侧位置,可以设置为像素值、百分比或预设的字符串值。

right: 网格组件右侧位置,同样可以设置为像素值、百分比或预设的字符串值。

top: 网格组件顶部位置,同样可以设置为像素值、百分比或预设的字符串值。

bottom:网格组件底部位置,同样可以设置为像素值、百分比或预设的字符串值。

containLabel: 网格是否包含坐标轴的刻度标签,默认值为false,表示不包含。

backgroundColor: 网格的背景色,可以设置为颜色值或渐变对象。

borderColor: 网格的边框颜色,可以设置为颜色值。

borderWidth: 网格的边框宽度,可以设置为像素值。

tooltip: 网格的提示框配置,可以覆盖全局的tooltip配置

xAxis 和 yAxis

直角坐标系中的 x 轴,支持多个坐标系和轴线样式的配置,

直角坐标系中的 y 轴,支持多个坐标系和轴线样式的配置。

type: 坐标轴类型,可选值为’value’(数值轴)、‘category’(类目轴)、‘time’(时间轴)、‘log’(对数轴)。
name: 坐标轴名称,字符串类型。
nameLocation: 坐标轴名称的位置,可选值为’start’(起始位置)、‘middle’(中间位置)、‘end’(结束位置)。
nameGap: 坐标轴名称与轴线之间的距离。
nameTextStyle: 坐标轴名称的样式,同样是一个 JavaScript 对象,可以设置字体大小、颜色、字体粗细等样式属性。
axisLine: 坐标轴轴线的样式,是一个 JavaScript 对象,可以设置线条颜色、宽度、类型等样式属性。
axisTick: 坐标轴刻度线的样式,也是一个 JavaScript 对象,可以设置线条颜色、宽度、长度等样式属性。
axisLabel: 坐标轴刻度标签的样式,同样是一个 JavaScript 对象,可以设置字体大小、颜色、字体粗细等样式属性。
splitLine: 坐标轴网格线的样式,同样是一个 JavaScript 对象,可以设置线条颜色、宽度、类型等样式属性。
splitArea: 坐标轴刻度区域的样式,也是一个 JavaScript 对象,可以设置背景色、边框线样式等属性。

axisTick 

show: 是否显示刻度线。
alignWithLabel: 刻度线与刻度标签是否对齐,默认值为false。
interval: 刻度线的显示间隔,可以设置为数字,表示每隔多少个刻度线显示一个,也可以设置为字符串’auto’,表示自动计算间隔。
inside: 刻度线是否朝内显示,默认值为false。
length: 刻度线的长度,可以设置为像素值。
lineStyle: 刻度线的样式,同样是一个 JavaScript 对象,可以设置线条颜色、宽度、类型等样式属性。

series

数据系列,包括折线图、柱状图、散点图、饼图等,可以设置系列的类型、数据、样式和标注等属性.

type: 系列类型,可选值为’line’(折线图)、‘bar’(柱状图)、‘pie’(饼图)等。
name: 系列名称。
data: 系列的数据,是一个数组,数组中每个元素代表一个数据项。
itemStyle: 系列的样式,是一个 JavaScript 对象,可以设置线条颜色、宽度、类型等样式属性。
label: 系列的标签样式,同样是一个 JavaScript 对象,可以设置字体大小、颜色、字体粗细等样式属性。
barWidth: 柱状图的柱宽,可以设置为像素值或百分比。
barCategoryGap: 柱状图的柱间距离,可以设置为像素值或百分比。
barGap: 柱状图的系列间距离,可以设置为像素值或百分比。
smooth: 折线图是否平滑显示,默认值为false。
symbol: 折线图的标记点样式,可以设置为’circle’、‘rect’、'triangle’等值。
symbolSize: 折线图的标记点大小,可以设置为像素值。
radius: 饼图的半径,可以设置为像素值或百分比。
center: 饼图的圆心坐标,可以设置为像素值或百分比。
roseType: 饼图的玫瑰图类型,可选值为’radius’和’area’。
labelLine: 饼图的标签线样式,是一个 JavaScript 对象,可以设置线条颜色、宽度、类型等样式属性。
label: 饼图的标签样式,同样是一个 JavaScript 对象,可以设置字体大小、颜色、字体粗细等样式属性。

 参考地址:【 echarts 属性详解】

21.父元素和子元素宽高不固定,如何实现子元素水平垂直居中

方法一:利用定位属性(position)+位移属性(transform) 

父级元素
position: relative;
子级元素
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);

方法二:利用弹性盒模型(只给父级设置样式)

display: flex;  //把父级变成弹性盒
justify-content: center; //让子元素在水平方向上居中分布
align-items: center;  //让子元素在垂直方向上居中分布

方法三:利用弹性盒模型(给父级和子级设置样式)

父级元素
display: flex;(垂直居中)
子级元素
margin: auto;(水平居中)

父元素和子元素宽高不固定,如何实现子元素水平垂直居中

22.处理接口的返回数据 

处理前是这样的数据结构:

处理后:

用到的方法主要是数组的reduceconcat

  // 处理计算结果的数据
  handleChangeData = (data: any[]) => {
    if (data.length) {
      return data.reduce((acc: any, cur: any) => {
        console.log('acc',acc,'cur',cur);
        
        const dataArr = cur.data.map((item: any) => ({
          ...item,
          parentName: cur.name,
        }));
        return acc.concat(dataArr)
      },[]);
    }
    return []
  };
  this.mergedTotalArray= this.handleChangeData(this.change_data_cache)
  console.log("处理后", this.mergedTotalArray);

23.CSS-圣杯布局和双飞翼布局

参考地址:CSS-圣杯布局和双飞翼布局

这里再推荐一下阮一峰老师的CSS Grid 网格布局教程:

CSS Grid 网格布局教程

24.突变状态 

先来看这个常见的页面:

代码:

每当增加一个新项目时,handleAddItem 函数就会被调用。但是,它并不起作用!当我们输入一个项目并提交表单时,该项目没有被添加到购物清单中。

问题就在于我们违反了也许是 React 中最核心的原则 —— 不可变状态。React依靠一个状态变量的地址来判断状态是否发生了变化。当我们把一个项目推入一个数组时,我们并没有改变该数组的地址,所以 React 无法判断该值已经改变。

正确的做法是:

function handleAddItem(value) {
  const nextItems = [...items, value];
  setItems(nextItems);
}

不建议修改一个现有的数组,而是从头开始创建一个新的数组。这里的区别在于编辑一个现有的数组和创建一个新的数组之间的区别。

同样的,对于对象类型的数据也是:

// ❌ 不建议
function handleChangeEmail(nextEmail) {
  user.email = nextEmail;
  setUser(user);
}

// ✅ 推荐
function handleChangeEmail(email) {
  const nextUser = { ...user, email: nextEmail };
  setUser(nextUser);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React前端电商项目是一种使用React框架搭建的电商网站前端项目。它基于React的组件化思想,将页面拆分成多个可复用的组件,使开发更加高效和模块化。 在React前端电商项目,常见的功能包括用户登录注册、商品展示、购物车、下单结算等。用户可以通过注册和登录功能,创建自己的账号,方便进行商品购买和订单管理。 商品展示是项目的核心部分,通过React的虚拟DOM技术,可以实现页面的快速渲染和实时更新。同时,通过组件化的思想,可以将商品信息、图片、价格等数据动态地渲染到页面上,提供给用户直观的浏览和选择。 购物车是电商项目的重要功能,用户可以将自己喜欢的商品加入购物车,通过React的状态管理机制,可以实现购物车数量的实时更新和显示。用户可以对购物车的商品进行增删改查等操作,方便进行商品管理和订单结算。 在下单结算部分,用户可以选择需要购买的商品和数量,并进行价格计算、收货地址选择等操作。通过React的表单处理技术,可以方便地获取用户输入的信息,并进行验证和提交操作。 总结来说,React前端电商项目具有模块化、高效、灵活等特点,能够为用户提供良好的购物体验。它使用React框架进行开发,借助其组件化和状态管理等特性,可以快速构建出功能完善的电商网站前端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值