编程崽

登录

一叶在编程苦海沉沦的扁舟之上,我是那只激情自射的崽

React 项目基础开发

React 项目基础开发

创建项目

使用大名鼎鼎的 cracreate-react-app)创建react项目

1. 首先使用 npm 全局安装

这一步其实可以省略,因为可以使用 npx,见下一步。

sh 复制代码
npm i -g create-react-app

2. 使用 create-react-app 创建项目

使用 create-react-app [项目名称] 的格式创建项目:

sh 复制代码
create-react-app react-demo

# 将在当前终端的文件夹创建一个名称为 react-demo 的文件夹
# 文件里面就是项目了

上一步说可以不全局安装 create-react-app,是因为现在的 node 除了 npm ,还提供了 npx 这个工具。

npx 允许用户使用一些需要先提前全局安装的工具时,比如 pm2cnpmyarn 等,可以不全局安装了,可以在这些指令的使用语句的前面,直接添加 npx

这样的好处是,一些使用频率很低的工具,不用全局安装了,而是使用 npx 来「临时」使用。

比如没有全局安装 create-react-app 时,直接使用下面的指令:

sh 复制代码
npx create-react-app react-demo

npx 如果检测到系统没有这个指令,就会先自动先下载这个工具,然后再使用这个工具执行后面的命令,最终所有指令完成后,这个工具会自动移除(实际可能是会只存在缓存区)。

项目编译的配置

完成安装后,可以进行一些常用配置。

修改webpack配置的途径

新版的 create-react-app 是隐藏配置的,想修改 webpack 配置,最傻瓜式的方式是执行 cra 提供的下面的这条指令,弹出所有 webpack 配置来修改,但这么操作实在太不友好了,所有配置文件夹、配置文件都出现在项目里,很杂乱且不能再收回去:

sh 复制代码
# 这条指令就在 create-react-app 创建的项目的 package.json 的指令中
npm run eject

因为上面的操作很不友好,所以我们一般会采用其他的工具来merge式的修改配置,最常用的工具就是 react-app-rewired 了。

先在项目中安装工具:

sh 复制代码
npm i -D react-app-rewired

然后修改 package.json 中的指令:

diff 复制代码
{
  "scripts": {
-    "start": "react-scripts start",
-    "build": "react-scripts build",
+    "start": "react-app-rewired start",
+    "build": "react-app-rewired build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  }

接着在项目根目录(package.json同级) 创建文件 config-overrides.js,内容如下:

js 复制代码
const path = require('path');

module.exports = (config, env) => {
  // env 顾名思义,就是环境变量配置
  // config 就是 webpack 配置文件,可以修改里面的项,最后会并入 webpack 的配置中
  // 从 config 可以读取到 entry、output、plugins、resolve……等等配置,可以进行修改

  // 查看 config.entry 配置
  console.log(config.entry)
  
  // 修改配置
  config.resolve = {
    ...(config.resolve ||  {}),
    alias: {
      ...(config.resolve.alias ||  {}),
      // 添加别名 alias
      app: path.join(__dirname, "src"),
    },
    // 针对 webpack >= 5 时需要单独导入node模块的配置
    // fallback: {
    //   ...(config.resolve.fallback || {}),
    //   crypto: require.resolve('crypto-browserify'),
    //   path: require.resolve('path-browserify'),
    //   http: require.resolve('stream-http'),
    //   https: require.resolve('https-browserify'),
    //   stream: require.resolve('stream-browserify'),
    // }
  }

  return config
}

.env文件进行配置修改

根目录下的 .env 文件同样可以做一些配置,比如:

sh 复制代码
# 本地启动服务时不自动使用浏览器打开页面
BROWSER=none

# 修改启本地服务时的端口号
PORT=3012

# 打包时不构建sourceMap文件
GENERATE_SOURCEMAP=false

3. 打包后的引入静态资源时使用相对路径

默认情况,打包后生成的静态项目,里面引入js\css\图片\字体时,使用的是绝对路径 /,这就导致我们无法双击index.html来预览项目(就算react路由模式使用的是hash模式也不行)。

所以可以给 package.json 文件添加一项:

json 复制代码
{
  // ...其他配置
  "homepage": "./",
}

这样打包完成后,引入文件的方式就都是相对路径 ./ 的形式了。

项目开发的配置

项目开始开发后,需要进行项目调整,比如配置路由、添加sass支持等。

首先是上面说到的修改 webpack 配置的工具 react-app-rewired,这里不再赘述。

less 还是 scss

node-sass 经常出问题,现在官方也已经不推荐使用 node-sass ,明确 node-sass 以后只会进行对新版本node的兼容更新,不会再添加新功能。

官方现在的建议,是使用 dark-sass,而 dark-sass 只是这种用法的名字,具体的使用,就是安装下面两个插件:

sh 复制代码
npm i -D sass sass-loader

安装上面两个依赖后,可实现 node-sass 同样的效果(样式穿透的写法有不同,见其他文档)。

此外,也可以使用less,而且,如果使用antd样式框架,那最好使用less,因为antd使用的就是less,如果想自定义修改antd的全局样式变量,就必须使用less了。

全局状态管理 redux

用处不用说了,首先安装需要的依赖:

sh 复制代码
npm i -S redux react-redux redux-thunk

接着创建文件夹 src/store,里面创建四个文件:

  • defaultState.js:全局状态数据的默认值们。
  • reducers.js:数据的分发。
  • actions.js:管理数据的 action 动作们。
  • index.js::把上面的东西统一规整暴露出去的文件。

下面一次性把四个文件中的内容大致写一下:

js 复制代码
// defaultState.js 文件中
const defaultState = {
  flagNum: 0,
};
export default defaultState;

// reducers.js 文件中
import { combineReducers } from "redux"; // 工具函数,用于组织多个reducer,并返回reducer集合
import defaultState from "./defaultState"; // 默认值
// 测试字段
function flagNum(data = defaultState.flagNum, action) {
  switch (action.type) {
    case "SET_FLAGNUM":
      return action.data;
    default:
      return data;
  }
}
// 导出所有reducer
export default combineReducers({
  flagNum,
});

// actions.js 文件中
export function init() {
  return async (dispatch, getState) => {
    const state = getState()
    const text = `页面初始化时 flagNum 的值:${state.flagNum}`
    console.log(text);
    // 是否 return 都可以
    return new Promise(resolve => {
      resolve();
    })
  };
}

// index.js 文件中
import { applyMiddleware, createStore } from "redux";
// 中间件,作用:如果不使用该中间件,当我们dispatch一个action时,需要给dispatch函数传入action对象;
// 但如果我们使用了这个中间件,那么就可以传入一个函数,这个函数接收两个参数:dispatch和getState。
// 这个dispatch可以在将来的异步请求完成后使用,对于异步action很有用
import thunk from "redux-thunk";
// 引入reducer
import reducers from "./reducers.js";
// 创建store实例
let store = createStore(reducers, applyMiddleware(thunk));
export default store;

下面把 redux 注入到全局,src/index.js 中:

js 复制代码
import React from 'react';
import ReactDOM from "react-dom/client";
import Router from "./router";
// 引入redux
import { Provider } from "react-redux";
import store from "app/store";

const root = ReactDOM.createRoot(document.getElementById('root'));
// 修改写法,使用 Provider 包裹并注入 store
root.render(
  <Provider store={store}>
    <Router />
  </Provider>
)

最后是在某个组件中使用,这里以 react 推荐的函数式组件为例:

js 复制代码
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
// 引入要使用的动作
import { init } from "../../store/actions";

export default function Button() {
  // 初始化 dispatch
  const dispatch = useDispatch()
  useEffect(() => {
    // 用 dispatch 执行再 actions 中写好的方法
    // 可以放在函数中,使用异步 async/await 执行
    dispatch(init())
  }, [dispatch])

  // 获取全局变量
  const flagNum = useSelector(state => state.flagNum);
  const addFlag = () => {
    // 用 dispatch 修改某个变量
    dispatch({ type: "SET_FLAGNUM", data: flagNum + 1 })
  }
  return (
    <button onClick={addFlag}>点击加值:{flagNum}</button>
  );
}

项目中报错

BREAKING CHANGE: webpack < 5 的报错

webpack 5 官方解决方案

最新版的 cra 项目中,使用node核心模块时,比如使用 path 时会发生类似以下报错:

sh 复制代码
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.

If you want to include a polyfill, you need to:
        - add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
        - install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
        resolve.fallback: { "path": false }

这是 webpack 新版本本身的特性,意思是webpack < 5版本时,自带node.js的核心模块,但大于等于5时,不会了,如果项目想使用类似 path、http、fs……这些模块时,需要自己安装对应的模块并进行webpack配置,才能使用了(还有一个办法,就是不使用webpack了,这种因噎废食的做法应该很少人采用)。

具体修正方法,见我这里另一个文档 webpack 版本5的报错