最近学习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的理解也上升了一个层次。
如果你也做到这里相信你也会有这种感受
努力、奋斗