React学习笔记
1).React核心概念
-
虚拟DOM(Virtual Document Object Model)
假如存在如下的Html结构,在js中可以获取到对应的DOM对象,因为JS中的DOM映射机制,可以通过操作js中的DOM对象来完成对页面的重排或重绘。但是DOM的操作成本是比较高的,所以引入虚拟DOM的概念。`所谓虚拟DOM就是与真实的DOM对象一一对应的普通JS对象
<div id="div1" style="background:red">
Hello 简书!
<p id="p1">可以在这里做笔记哦</p>
</div>
var vDom={
id:"div1",
style:"background:red",
children:[
"Hello 简书!",
{
id:"p1",
children:["可以在这里做笔记哦"]
}
]
}
-
Diff算法(分以下三步)
- tree diff:新旧两颗DOM树,逐层对比的过程就是Tree Diff;当整颗DOM树对比完毕,则可以保证所有需要更新的元素必然可以找到。
- component diff:对于组件化开发,DOM树中的每个层级都是各种的组建,在进行Tree Diff的时候,每一层中,组件级别的对比,叫做Component Diff
1.如果对比前后,组建的类型相同,则暂时认为此组件不需要被更新
2.如果对比前后,组建的类型不同,则需要移除旧的组建,创建新组建并追加到页面上。- element diff:在进行组建对比的时候,如果两个组建类型相同,则需要进行元素级别的对比,叫做Element Diff
2).使用webpack4.x构建项目
- 1.初始化项目,创建一个目录,在根目录下运行
npm init
。(npm init -y快速初始化项目) - 2.在项目根目录创建src源代码目录和dist产品目录
- 3.在src目录下创建一个
index.html
以及index.js
入口文件(这里填写自己的js代码) - 4.安装webpack,npm i webpack --save-dev(在当前目录下安装),webpack默认只能打包处理.js文件,其他格式的需要配置第三方loader规则,webpack4.x具有约定大于配置的概念:目的是为了尽量减少配置文件的体积
- 默认约定了打包的入口文件是
src/index.js
- 打包的输出文件时
dist/main.js
- 默认约定了打包的入口文件是
- 5.安装webpack-cli,在package.json的script添加编译命令
"build":"webpack"
- 6.在根目录下创建webpack.config.js文件,在
webpack.config.js
中指定modemodule.exports = { mode: 'production' //or production };
- 5.设置打包命令,在根目录
package.json
的scripts
节点添加"build": "webpack"
,就可以终端运行npm run build
来打包代码了,可以看到在dist下有一个main.js
的输出文件 - 6.运行
npm i webpack-dev-server --save-dev
安装webpack-dev-server - 7.设置编译发布命令,在根目录
package.json
的scripts
节点添加"dev": "webpack-dev-server"
,就可以通过执行npm run dev
命令来将网站托管在localhost的端口上,并且代码修改可以及时反映在浏览器中而不用执行编译脚本"dev": "webpack-dev-server --open chrome --host 127.0.0.1 --port 3000 --hot"
//编译发布 在浏览器中打开 设置host 设置port 热替换 - 8.添加js引用,在
src/index.html
添加对/main.js
的引用webpack-dev-server打包好的main.js是托管在内存中的(避免频繁ctrl+s频繁编译),所以在磁盘的根目录中看不到,但是可以认为,在项目根目录下有一个看不见的main.js
3).在项目中使用React
- 1.安装React包,运行npm i react react-dom --save(--save or -S在开发/生产都要用的的安装包)
- react:专门用于创建组件和虚拟DOM的,同时维护组件的生命周期
- react-dom:专门进行DOM操作的,主要的应用场景,就是ReactDOM.render()
- 2.在
index.html
页面中,创建react渲染虚拟DOM的容器<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>home page</title> </head> <body> <!-- React容器 虚拟DOM在这里渲染 --> <div id="app"></div> </body> <!-- 切记:这里不可以把main.js在head中引用,因为这时这是body还没有加载,但是在 js中react已经用到了app容器 --> <script src="/main.js"></script> </html>
- 3.在入口文件
index.js
中导入包、创建虚拟DOM元素、使用ReactDOM将虚拟DOM元素渲染到页面容器中//1.引入React包,以下两个react包导入的时候必须用以下两个名字接收 import React from 'react' //创建组件、虚拟DOM元素、生命周期 import ReactDOM from 'react-dom' //在页面渲染创建好的组件和虚拟DOM //2.创建虚拟DOM元素 //param1:字符串 创建的元素名称 //param2:对象或null 表示当前这个DOM元素的属性 //param…:子节点 (其他若干参数) const myh1 = React.createElement('h1',{id:'myh1',title:'this is a h1'},'this is a h1 element') //<h1 id='myh1' title='this is a h1'>this is a h1 element</h1> //3.使用ReactDOM把虚拟DOM渲染到页面上 //param1:要渲染的虚拟DOM元素 //param2:指定一个DOM元素,作为渲染虚拟DOM的容器 ReactDOM.render(myh1,document.getElementById('app'))
- 4.启用JSX语法(符合XML规范的js语法,类似与HTML)
- 安装babel插件:
npm i babel-core babel-loader babel-plugin-transform-runtime --save-dev
npm i babel-preset-env babel-preset-stage-0 --save-dev
- 安装能够识别JSX语法的包:
npm i babel-preset-react -D
- 在项目根目录添加
.babelrc
配置文件,该文件为JSON格式{ "presets":["env","stage-0","react"], "plugins":["transform-runtime"] }
- 在webpack配置文件中,配置
babel-loader
对jsx语法的解析规则//webpack.config.js module.exports = { mode: 'development', module:{//配置loader 所有第三方模块的配置规则 rules:[ {test:/\.js|jsx$/,use:'babel-loader',exclude:/node_modules/} ] } };
在JSX语法区域内,如果需要写JS表达式,则需要把JS代码写在{}中
- 安装babel插件:
4).React组件的两种创建方式
- 1.构造函数方式
- 组件如果需要接收外界的数据,需要在构造函数列表中使用
props
来接收,props
就相当于DOM元素的属性一般赋值。 - 在一个组件中,必须要一个返回值,这个返回值是一个合法的JSX虚拟DOM元素,如果
return null
,则表示此组件是空的. - props是只读的,props需要用形参接收。
- 组件的首字母必须大写
function Hell(props){ return <div>{{props.name}}</div> } ReactDOM.render(<div> <Hello name='kerry'></Hello> </div>,document.getElementById('app'))
- 组件如果需要接收外界的数据,需要在构造函数列表中使用
- 2.Class方式
- 使用class定义组件,必须让组件继承自React.Component
- 组件内部必须有render函数,render函数必须返回合法的JSX虚拟DOM结构
- 最基本的组件结构:
- props是只读的,在class中可以通过this.props使用
- 有自己的私有数据
state
,class内部自身维护的可读写的数据,不通于props
是通过外界传递而来
class 组件名称 extends React.Component{ constructor(){ super(); this.state={} } render(){ return <div>{this.props.name}</div> } }
- 3.异同
- 使用class创建的组件,有自己的私有数据
state
和生命周期函数,叫做“有状态组件
” - function创建的组件,只有props,没有自己的私有数据和生命周期函数,叫做“
无状态组件
” 有状态组件与无状态组件的本质区别是有无state
- 使用class创建的组件,有自己的私有数据
5).React中的元素样式
React中的元素样式属性名也是style,但是不接收字符串,而是以双层大括号包裹,外层大括号表示在JSX中的JS代码,内层大括号表示一个对象。且样式属性对象的key必须是小写字母开头的驼峰写法
-
1.内联样式
function ItemComp(props){ //1.直接将样式写在元素内 // return <li style={{color:'red',txetAlign:'center',width:'60%'}}> // <span style={{fontSize:'14px'}}>id:{props.id}</span> // <span style={{float:'right',display:'inline-block'}}>code:{props.code}</span> // </li>; //2.将样式封装成对象,也可以抽出到一个单独的js文件中然后import const styles={ li:{color:'red',txetAlign:'center',width:'60%'}, spanId:{fontSize:'14px'}, spanCode:{float:'right',display:'inline-block'} } return <li style={styles.li}> <span style={styles.spanId}>id:{props.id}</span> <span style={styles.spanCode}>code:{props.code}</span> </li>; }
-
2.外联样式(样式表)
- 2.1 安装样式相关的包,运行
npm i style-loader css-loader -D
- 2.2 webpack.config.js的module的rules节点配置cssloader
let path=require('path') module.exports = { mode: 'development', module:{//配置loader 所有第三方模块的配置规则 rules:[ {test:/\.js|jsx$/,use:'babel-loader',exclude:/node_modules/},//exclude,排除项 {test:/\.css$/,use:['style-loader','css-loader']}//配置打包处理css样式表的第三方loader,webpack的loader匹配规则是从右到左 ] }, resolve:{ extensions:['.js','.json','.css'],//表示在引用这几种后缀名的文件时,可以省略不写后缀名 alias: {//声明别名,编译的时候会按照这里的规则将别名替换掉 '@': path.join(__dirname,'./src') } } };
- 2.3 新建一个样式表,按照css语法书写样式
.itemli{ color:red; text-align:center; width:60% }
- 2.4 在需要使用样式的组件中导入样式表文件
import stylesObj from './../css/itemcomp.css';
- 2.5 在需要应用样式的元素中添加className属性
return <li className="itemli">列表项</li>;
但是,使用上述方式定义的样式是全局的,一次引用,每个组件都可以使用,容易造成样式混乱,因为样式表不同于JS没有作用域的概念
,那么怎么解决这个问题呢? - 2.1 安装样式相关的包,运行
-
3.模块化的样式表
- 3.1. 为css-loader配置参数以支持普通的css样式表启用模块化
//css-loader可以通过?追加参数 //modules表示为普通样式表启用模块化 {test:/\.css$/,use:['style-loader','css-loader?modules']}//配置打包处理css样式表的第三 方loader,webpack的loader匹配规则是从右到左
- 3.2. 启用模块化样式表后,import的样式表不同于未启用模块化的空对象,是具有值 的
import stylesObj from './../css/itemcomp.css'; console.log(stylesObj);
![image.png](https://img.haomeiwen.com/i3566275/ cc80053689b64f1b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
- 3.3. 为需要的元素添加className或id(css-loader模块化只是针对class或id选择器生 效),
<li className={stylesObj.itemli}>
- 3.4. 非模块化与模块化的样式表异同在于
*非模块化的样式表编译后,样式表解析在index.html的<style>标签中,类名同于样式表的类名
*模块化样式表编译后,样式表同样解析在index.html的<style>标签中,不同的是,类名是被混淆的而非样式表中原始类名
![模块化样式表编译后](https://img.haomeiwen.com/i3566275/ 4c40688f04a446a9.png?imageMogr2/auto-orient/strip%7CimageView2/3/w/500)
![非模块化样式表编译后](https://img.haomeiwen.com/i3566275/ 58ba9610b50df1aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/500)
-
4.自定义样式表模块化名称
开启css-loader的模块化会对样式表中的类名进行混淆,如果不想要混淆,而是通过一定规则定义的怎么做呢?使用css-loader的
使用localIdentName参数自定义的stylesObjlocalIdentName
参数自定义生成的类名格式,可选参数有
- [path]表示对样式表编译后生成的类名具有相对于项目根目录所在的路径
- [name]表示生成的类名具有样式表文件名
- [local]表示生成的类名具有样式表中定义的原始类名
- [hash:length]表示生成的类名具有length长的的hash值
如图所示,四段分别对应四个可选参数,如此便可以看到有意义的className{test:/\.css$/,use:['style-loader','css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]']}