徒手撸个react项目框架(上)

最近学习react.js,发现项目框架除了使用的js库不同(vue.js、react.js),配置基本上是大同小异的

徒手撸个vue项目框架(上)
徒手撸个vue项目框架(下)
徒手撸个react项目框架(上)
徒手撸个react项目框架(下)

一、准备工作

1.新建vueProject文件夹

进入根目录,初始化项目

cd vueProject

npm init -y // -y是采用默认配置

此时目录出现package.json文件

2.创建项目结构

在根目录下新建src文件夹,在src下暂时新建名为index的js文件作为入口文件

根目录下创建一个index.html,作为入口页面

3.使用webpack

下载webpack时你可能会出现无限下载webpack-cli的问题,这是因为你没有先全局安装webpack和webpack-cli的原因

// webpack4.X开始webpack-cli被提出来作为一个独立的包了
// 在下载webpack同时也要下载webpack-cli,且必须同时下载否则会报错,因为版本不匹配
cnpm install webpack-cli webpack --save-dev

webpack默认只能打包js模块,它可以将你写的多个js模块打包成一个js文件,最后在入口页面引入它

webpack4开始默认大于配置,换句话说可以不用再引入一个配置文件来打包项目,因此他有很多默认值

默认入口文件是src下的index.js,输出为dist目录下的main.js(假如没有dist目录会自动创建)

但是它仍然是高配置的,假如需要我们只需在项目根目录下新建webpack.config.js来进行一切的配置

相比于webpack4之前的版本,它的配置项多出一个mode选项,可选值为"development" 或 “production”(默认),它们的区别就是development打包输出的文件不是压缩版本的

4.使用react.js

react和vue不同的是,react使用两个包协同工作

  • react包:负责组件或者虚拟dom
  • react-dom包:负责将组件或者虚拟dom插入到根节点
cnpm install react react-dom --save-dev

index.js中

// index.js
import React from 'react'
// 这个包名必须这样写
import ReactDOM from 'react-dom'
/*
*createElement: 创建虚拟dom元素
* 第一个参数为标签类型
* 第二个参数为标签属性对象
* 剩余参数皆为参数为子节点
*/
const dom = React.createElement("h1",{id: "test"},"hello react")
// render函数是将虚拟dom插入目标容器Target container 
ReactDOM.render(
    dom,
    document.getElementById("root")
)

index.html中

//  index.html
<div id ="root"></div>

页面中会看到hello react的字样,审查元素如下

<div id="root"><h1 id="test">hello react</h1></div>

这说明我们的准备工作都成功完成了

二、完善框架功能

1.使用webpack-dev-server

每次写完新的内容要想看到效果,就必须使用webpack进行打包,我们更希望当代码改变时自动打包编译

webpack-dev-server可以帮我们做到!

a.下载
cnpm i webpack-dev-server --save-dev
b.使用

我们不能像使用webpack命令一样使用webpack-dev-server命令,因为webpack-dev-server是局部安装的,而令行里只能使用全局安装的包,使用局部安装的包,我们需要使用在package.json中配置scripts

// package.json
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --hot"
},

然后再命令行使用npm run dev

npm run dev

注意看下面的节选的代码

npm run dev

> vueproject@1.0.0 dev C:\myProject\reactProject
> webpack-dev-server

i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /
i 「wds」: Content not from webpack is served from C:\myProject\reactProject
i 「wdm」: Hash: e70fb3ae9bf074915cad
Version: webpack 4.35.0

从这里我们知道两件事:
首先,我们的项目运行在本机8080端口,其次webpack的output输出在根目录下,所以记得修改index.html中引入main.js的路径,否则你是看不到新的效果的

但是我们在根目录下并没有看到这个文件,这是因为它被放在内存中(这样的读写速度快),而不是磁盘中,另外我们还可以修改端口,甚至可以在编译完成后自动打开浏览器

它具体的配置可以是在webpack的devServer

devServer:{
    host: '127.0.0.1',
    port: 8081,
    open: true
}

也可以是在cli里,这是最暴力的方式,但是端口还是放在devServer里,方便以后项目的配置

// package.json
"scripts": {
    "dev": "webpack-dev-server --open --port 30000"
},
2.使用html-webpack-plugin

既然将main.js放在内存中可以加快读写速度,那是不是把页面放在内存中可以进一步加快读写速度了?

答案是肯定的!使用html-webpack-plugin就可以做到

a.下载
cnpm i html-webpack-plugin --save-dev
b.使用
// webpack.config.js
const htmlWebpackPlugin = require('html-webpack-plugin')
...js
plugins: [
    new htmlWebpackPlugin({
      template: path.join(__dirname, "./index.html"),
      filename: "index.html"
    })
  ],

上面的代码是根据磁盘中的index.html在内存中生成一个index.html,我们在浏览器中审查页面发现会多一个script标签,这是插件自动将内存中的main.js加入到内存页面中了,所以我们这是应该删除手动添加的script标签

// index.html
<body>
    <div id="app"></div>
    <!--删除或者注释掉 <script src="./main.js"></script> -->
</body>

现在它已经可以自己跑起来并自动监听变化做出反应了

三、继续完善框架(项目)功能

1.使用jsx语法

虽然开起来已经很完美了,但是像前面面那样写react虚拟dom总是觉得很复杂,作为前端工程师,我们更希望写的dom就是这样的,然后把它和之前一样插入

const dom = <h1 id="test">hello react</h1>
ReactDOM.render(
    dom,
    document.getElementById("root")
)

这完全是可以的,上面的写法就是react的扩展语法jsx,使用jsx语法必须使用babel-loader

a.下载

有了前面文章的经验,我发现如果现在下载babel-loader最好使用下面的形式,因为babel已经更新到7.x.x了,写法和之前的版本有很大差异,否则总是会出现一些版本不兼容的错误

//下载loader加载器
cnpm i @babel/core babel-loader --save-dev
//下载插件
cnpm i @babel/plugin-transform-runtime --save-dev
//下载预设
cnpm i @babel/preset-env @babel/preset-react --save-dev
b.使用

babel的两种使用方法前面已经说过了,这里再简单的说说官网给出的别的简单方法

第一种方法,在rules中配置babel-loader同时提供options选项

// 第一种写法。
//除了基本的配置test外,可以使用options,里面必须有presets和plugins两个选项
// presets是预设的babel语法转换规则,这里除了使用最新的规则外还是用了针对react的jsx转换规则
// plugins是指明使用的插件
module: {
    rules: [
      {
        test: /\.js|jsx$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"],
            plugins: ["@babel/plugin-transform-runtime"]
          }
        }
      }
    ]
  },

第二种方法根目录新建.babelrc文件,格式同json

{
    presets: ["@babel/preset-env", "@babel/preset-react"],
    plugins: ["@babel/plugin-transform-runtime"]
}
//它会自动识别到这个文件
//此时可以删掉第一种方法中的options选项

第三种方法在package.json中添加babel节点

{
  "name": "my-package",
  "version": "1.0.0",
  "babel": {
    "presets": ["@babel/preset-env", "@babel/preset-react"],
    "plugins": ["@babel/plugin-transform-runtime"]
  }
}

第四种方法是根目录新建.babelrc.js 配置和前面都一样,只是可用js编写

const presets = ["@babel/preset-env", "@babel/preset-react"];
const plugins = ["@babel/plugin-transform-runtime"];
// 这种写法的好处是你可以调用node API
if (process.env["ENV"] === "prod") {
  plugins.push(...);
}
module.exports = { presets, plugins };

当然还有cli方法,或者选择转换node_modules目录的babel.confog.js的方法,但都是比较复杂或者冷门的

2.使用样式表

在src下新建了一个css样式表文件夹,里面写了自己针对自己组件的样式,然后在需要的jsx文件中引入它,像我这样

/* css/index.css文件 */
.test{
    color: green   
}
//在index.jsx文件中引入样式
import React from "react";
import "@/css/index.css"

export default function Index() {
    return <div className="test" id="index">index page</div>;
}

//在login.jsx中不引入样式,但是添加class
import React from "react";

export default class Login extends React.Component {
  render() {
  // 因为class是js的关键字,所以jsx中使用className作为html的class
    return <div className="test">login page</div>;
  }
}

这时候会报错,提示使用合适的loader

和vue一样要使用样式表必须使用loader,因为它们都是jsx语法生成虚拟dom的,而jsx无法解析样式表

a.下载
cnpm i style-loader css-loader --save-dev
b.使用

下载完之后和vue项目一样配置文件

rules: [
    ...
    {
        test: /\.css$/,
        use: ["style-loader","css-loader"]
    }
]
c.问题

虽然这时候可以使用样式了但是,你会发现你没有引入样式的login组件页面也是绿色字体了
在这里插入图片描述
这是因为webpack会将所有的样式放在一个style标签然后插入head标签下,它的作用域是全局了,审查元素你看到的是这样的
在这里插入图片描述
同样的问题,vue可以在style标签添加scoped指令来控制样式作用域,原理就是在在样式表中选择器前加个组件的标识,react中没有指令的概念,怎么解决了这个问题了?

好在css-loader可以提供模块化功能,我们只需如下改动

rules: [
    ...
    {
        test: /\.css$/,
        // 这种写法和url带参数很类似,这是开启模块化的意思
        use: ["style-loader","css-loader?modules"]
    }
]

此时我们的引入css样式需要一个参数去接收css暴露出来的模块(当然这是css-loader的功能)

//在index.jsx文件中引入样式
import React from "react";
import indxCss from "@/css/index.css"
console.log(indxCss) // => {test: "q7KCiLIWvHKVJp6HMfV2y"}
export default function Index() {
    return <div className={indxCss.test} id="index">index page</div>;
}

同时说个有意思的事,css-loader除了modules参数外,还有很多参数
如果你不喜欢随机的字符串做样式的标识,可以设置localIdentName参数,它是以下方式的组合

  • path: 文件的路径
  • name: 文件名称
  • local:样式名称
  • hash:32哈西值,后面可以定义长度
rules: [
    ...
    {
        test: /\.css$/i,
        // 下面这种写法是老版本的写法,会一直报错无效的版本,别问我是怎么知道的,webpack官网没有提示
        //use: ["style-loader","css-loader?modules"],
        // 新版本3.0.0是这样的
        // 去css-loader的npm官网才能看到这种写法
        use: [
          { loader: "style-loader" },
          {
            loader: "css-loader",
            options: {
              modules: {
                localIdentName: "[path][name]-[local]-[hash:base64:5]"
              }
            }
          }
        ]
    }
]

虽然现在看上去是完美的,但是有个问题是假如现在引入第三方库,它也是css文件,也会被模块化的,但我们希望它是全局有效的,比较好的做法就是第三方库采用css样式,而自己的样式启用scss或者less,所以你得安装它们的loader

cnpm i less-loader --save-dev

添加loader

rules: [
    ...
    {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
     },
    {
        test: /\.less$/i,
        use: [
          { loader: "style-loader" },
          {
            loader: "css-loader",
            options: {
              modules: {
                localIdentName: "[path][name]-[local]-[hash:base64:5]"
              }
            }
          },
          {loader: "less-loader"}
        ]
    }
]
3.使用图片

如果在样式中添加如下样式

.test{
    background-image: url(../imgs/11.jpg)
}

会发现jsx也无法处理图片url的,所以得添加loader去处理这些

a.下载

url-loader内部使用了file-loader,所以两个loader要同时下载

cnpm i url-loader file-loader --save-dev
b.使用

基本的使用如下

module: {
    rules: [
        ...
        {
            test: /\.(png|jpe?g|gif|svg|eot|ttf|woff|woff2)$/,
            loader: "url-loader"
        }
    ]
  },
c.options

这个和css-loader设置一样

 module: {
    rules: [
      {
        test: /\.(jpg|png|jpeg)$/,
        loader: "url-loader",
          options: {
            limit: 8000,    // 当文件字节大小超过限定值时触发后面的设置
            name: "[hash:8]-[name].[ext]"  // 这是在原先的名称和后缀名前加了八位的哈希码
          }
      }
    ]
  },
四、结语

目前基本的功能都有了,但是还是不够完美,下期将会引入react-router,对业务进行封装。从目前来看,不管vue项目框架还是react框架,有极大的相似之处,学了vue学react就简单多了,而且这是学习react对vue的理解也上升了一个层次。

如果你也做到这里相信你也会有这种感受

努力、奋斗

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值