1.react与vue的对比
1.1什么是模块化
是从代码的角度进行分析的,把一些可复用的代码,抽离为单个的模块,便于项目的维护和开发。
1.2什么是组件化
是从UI界面的角度来进行分析的,把一些可复用的UI元素,抽离为单独的组件,便于项目的维护和开发。
好处:随着项目规模的增大,手里的组件越来越多,很方便就能把现有的组件,拼接成一个完整的项目
1.3Vue如何实现组件化
通过 .vue 文件,来创建对应的组件,还可以通过Vue.component() Vue.extends() 创建组件
- template 结构
- script 行为
- style 样式
1.4react如何实现组件化
react中有组件化的概念,但并没有像vue这样的模板文件,react中,一切都是以 js 来表现的
2.react中几个核心的概念
2.1虚拟dom
2.1.1dom的本质
浏览器中的概念,用js对象来表示页面上的元素,并提供了操作dom对象的api
2.1.2react中的虚拟dom
用js对象来模拟页面上的dom和dom嵌套,目的是为了实现页面元素的高效更新
2.1.3为什么要实现虚拟dom
为了实现页面中,dom元素高效更新
2.1.4dom和虚拟dom的区别
dom:浏览器中提供的概念,用js对象,表示页面上的元素,并提供操作元素的api
虚拟dom:是框架中的概念,而是开发框架的程序员,手动用js对象来模拟dom元素和嵌套关系
2.2dom树
<div id="mydiv" title="嘻嘻">今天天气真好</div>
var div={ tagName:'div', attrs:{ id:'mydiv', titie:'嘻嘻' }, childrens:[ '今天天气真好' ] }
2.3diff算法
- tree diff:新旧两颗dom树,逐层对比的过程,就是tree diff,当整颗dom树逐层对比完毕,则所有需要按需更新的元素必然能够找到;
- component diff:在进行tree diff的时候,每一层中,组件级别的对比,叫做component diff,如果对比前后,组件的类型相同,则暂时认为此组件不需要更新,如果对比前后组件的类型不同,则需要移除旧组件,创建新组件,并追加到页面上;
- element diff:在进行组件对比的时候,如果两个组件类型相同,则需要进行元素级别的对比,这叫做element diff;
3.搭建webpack4.x项目
创建一个文件夹,用vs code打开
1. 运行 npm init -y 快速初始化项目
2.在项目根目录创建src源代码目录和dist产品目录
3.在src目录下创建index.html 和index.js
4.使用npm安装webpack,运行 npm i webpack -D
5.使用npm安装webpack-cli,运行 npm i webpack-cli -D
6.在package.json 修改如下配置,指定dve 为webpack
"scripts": { "test": "echo "Error: no test specified" && exit 1", "dev" : "webpack" },
7.在根目录下创建 webpack.config.js 文件,添加如下内容
//向外暴露一个打包的配置对象 module.exports={ mode: 'development' //production二选一 }
8.通过命令 npm run dev 来启动项目
注意:webpack 4.x 提供了约定大于配置的概念,目的是为了尽量减少配置文件的体积,默认约定了打包的入口src->index.js 打包的输出文件dist->main.js
9.配置项目自动更新打包
运行 命名 npm i webpack-dev-server -D 当项目修改后会进行自动打包,并修改配置文件package.json
"scripts": { "test": "echo "Error: no test specified" && exit 1", "dev" : "webpack-dev-server" },
注意:此时运行npm run dev 会报出如下错误,原因是webpack-cli版本太高
Error Cannot find module 'webpack-cli/bin/config-yargs'
解决办法:(1)npm uninstall webpack-cli 卸载当前的webpack-cli
(2)重新安装 webpack-cli 3.* 版本npm install webpack-cli@3 -D
10.在index.html中加入如下内容
<script src="/main.js"></script> <!--实际在内存中生产-->
11.将index页面导入内存中
使用命令 npm i html-webpack-plugin -D 安装插件
在webpack.config.js配置插件
const path=require('path') const HtmlWebpackPlugin = require('html-webpack-plugin') //创建一个插件的实例对象 const htmlPlugin = new HtmlWebpackPlugin({ template:path.join(__dirname,'./src/index.html'),//__dirname代表根目录,拼接源文件路径 filename:'index.html' }) //向外暴露一个打包的配置对象 module.exports={ mode: 'development', //production二选一 plugins:[ htmlPlugin ] }
12.重新npm run dev 就可以实现实时编译
4.在项目中使用react
4.1安装react
运行 npm i react react-dom -S 安装包
- react:专门用于创建组件和虚拟dom的,同时组件的生命周期都在这个包中
- react-dom:专门进行dom操作的,最主要的应用场景就是ReactDOM.render()
4.2初步使用react
在index.js 中配置虚拟dom
//这两个包名接收的成员名必须这么写 import React from 'react' //创建组件、虚拟dom元素,生命周期 import ReactDOM from 'react-dom' //把创建好的组件和虚拟dom 放到页面上展示 //创建虚拟dom元素 //参数1:创建的元素的类型,字符串,表示元素的名称 //参数2:是一个对象或null,表示 当前这个DOM 元素的属性 //参数3:子节点(包括其他虚拟DOM 获取文本子节点) //参数n:其他子节点 //<div id="weather" title="today">今天天气真好</div> const hh=React.createElement('div',{id:'weather',title:'today'},"今天天气真好") //使用ReactDOM把虚拟dom渲染到页面上 //参数1:要渲染的那个虚拟dom元素 //参数2:指定页面上的一个容器的dom元素 ReactDOM.render(hh,document.getElementById("app"))
在index.html中指定容器
<div id="app"></div>
然后访问页面就可以实现虚拟dom的渲染
使用React.createElement实现虚拟DOM嵌套
const hh=React.createElement('div',{id:'weather',title:'today'},"今天天气真好") const div=React.createElement('div',null,'这是一个div',hh) ReactDOM.render(div,document.getElementById("app"))
5.JSX
5.1什么是JSX
在js中,混合写入类似于HTML的代码,叫做JSX语法,符合xml规范的js,JSX 语法上更接近 JavaScript 而不是 HTML,例如:
const mydiv=<div id="mydiv" title="div aaa">这是一个div元素</div>
注意:JSX语法的本质,还是在运行的时候,被转换成了React.createElement形式来执行的
5.2如何启用JSX语法
使用jsx语法前首先要安装babel插件
- 运行 npm i babel-core babel-loader@7.1.5 babel-plugin-transform-runtime -D
- 运行 npm i babel-preset-env babel-preset-stage-0 babel-preset-react -D
在webpack.config.js配置如下内容
module: { //所有第三方 模块的配置规则 rules: [ //第三方匹配规则 { test: /.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ } ]
在根目录下创建一个.babelrc文件,加入如下配置
{ "presets": [ "env", "stage-0", "react" ], "plugins": [ "transform-runtime" ] }
5.3JSX语法
渲染常量
let a=10
ReactDOM.render(<div>{a}</div>,document.getElementById("app"))
三元表达式
let boo=false ReactDOM.render(<div>{boo?'条件为真':'条件为假'}</div>,document.getElementById("app"))
绑定属性
let title="999"
ReactDOM.render(<div>
<p title={title}>这是p标签</p>
</div>,document.getElementById("app"))
渲染HTML元素
const mydiv=<div id="mydiv" title="div aaa">这是一个div元素qqq</div> ReactDOM.render(<div> <p title={title}>这是p标签</p> <hr/> {mydiv} </div>,document.getElementById("app"))
渲染数组元素
const arr=[ <h2>这是h2</h2>, <h3>这是h3</h3> ] ReactDOM.render(<div> <p title={title}>这是p标签</p> <hr/> {arr} </div>,document.getElementById("app"))
外部数组遍历渲染forEach
const arrStr=['灰太狼','喜洋洋','美羊羊','懒羊羊','沸羊羊'] const nameArr=[] arrStr.forEach(item=>{ const temp=<h5>{item}</h5> nameArr.push(temp) }) ReactDOM.render(<div> {nameArr} </div>,document.getElementById("app"))
内部数组遍历渲染map
const arrStr=['灰太狼','喜洋洋','美羊羊','懒羊羊','沸羊羊'] ReactDOM.render(<div>{arrStr.map(item=><h3 key={item}>{item}</h3>)}</div>,document.getElementById("app"))
给数组绑定id:key={item}
JSX内部的注释:JSX内部的注释分为单行注释和多行注释两种,具体语法如下,推荐使用单行注释
{/*我是单行注释 */}
{ //我是多行注释 //我还可以写很多 }
在注释中可以使用//#region和//#endregion来折叠注释
为jsx中的元素添加class类名,需要用className来代替class,用 ‘htmlFor’ 替代 label 的 for 属性
6.react组件
6.1创建组件
6.1.1 使用function关键字创建
在组件中必须返回一个合法的JSX虚拟的DOM元素,组件的首字母必须大写
function Hello(){ return <div>这是H1</div>; }
为组件传递数据
const dog={ name:"大黄", age:2, gender:"男" } function Hello(props){ return <div>这是Hello组件----{props.name}----{props.age}---{props.gender}</div>; }
其中组件的使用还可以使用展开运算符:
<Hello {...dog}></Hello>
6.1.2 使用class关键字创建
最基本的组件结构
class 组件名称 extends React.Component{ render(){ return <div>这是class创建的组件</div> } }
在组件内部,必须有render函数,必须返回合法的JSX虚拟dom结构,render函数的作用:是渲染当前组件所对应的虚拟dom元素
6.1.3 两种创建组件方式对比
注意:使用class创建的组件,有自己的私有数据和生命周期函数
注意:使用function创建的组件,只有props,没有自己的私有数据和生命周期函数
- 1.用构造函数创建的组件叫做“无状态组件”
- 2.用class关键字创建的组件叫做“有状态组件”
- 3.什么情况下使用有状态组件?什么情况下使用无状态组件?
有状态组件和无状态组件的本质区别就是:有无state属性,这里的state相当于vue中的data
6.2 抽离组件
在项目根目录下创建一个包components,然后创建一个文件 Hello.jsx ,然后将组件注册写在这个文件里面
import React from 'react' export default function Hello(props){ return <div>这是Hello组件----{props.name}----{props.age}---{props.gender}</div>; }
使用抽离组件,在使用组件之前我们要先进行注册组件
import Hello from "./components/Hello.jsx";
注意,这里的jsx后缀名必须要写,不然会报错,但是我们可以通过配置来不写后缀名,可以通过@来配置绝对路径
在webpack.config.js里配置如下内容
resolve:{ extensions:['.js','.jsx','.json'], //表示这几个文件的后缀名可以不写 alias: { '@': path.join(__dirname,'./src') //这样@符合代表src的根目录 } }
6.3 组件属性props
在class关键字创建的组件中,如果想使用props参数,不需要接收,直接通过this.props.***访问即可
class Movie extends React.Component{ render(){ return <div>hhh+{this.props.name}----{this.props.age}</div> } }
组件传递如下
<Movie name={dog.name} age={dog.age}></Movie>
组件中的 props 和 state/data 之间的区别
- props中的数据都是外界传递过来的
- state/data中的数据,都是组件私有的,(通过Ajax获取回来的值,一般都是私有数据)
- props中的数据都是只读的,不能重新赋值
- state/data中的数据,都是可读可写的
6.4 for循环生成多组件
function CmtItem(props){ return <div> <h1>评论人:{props.user}</h1> <p>评论内容:{props.content}</p> </div> } class CmtList extends React.Component { constructor() { super() this.state = { CommentList:[ {id:1,user:'张三',content:'哈哈 沙发'}, {id:2,user:'李四',content:'哈哈 板凳'}, {id:3,user:'王五',content:'哈哈 凉席'}, {id:4,user:'赵六',content:'哈哈 砖头'}, {id:5,user:'田七',content:'哈哈 最后'}, ] } } render() { return <div> <h1>这是评论列表组件</h1> {this.state.CommentList.map(item=><CmtItem {...item} key={item.id}></CmtItem>)} </div> } } ReactDOM.render(<div> <CmtList></CmtList> </div>, document.getElementById("app"))
6.5 style的使用
6.5.1 行内使用
在使用style标签时,要用双大括号
注意:在行内样式中,如果是数值类型的样式,则可以不用引号包裹,则可以不用引号包裹,如果是字符串类型的样式值,必须使用引号包裹。
style={{color:'red',fontSize:'16px'}}
6.5.2 样式抽离
const styles={ item:{ border:'1px',margin:'10px'}, content:{ fontSize:'12px'} }
引入为 style={styles.item}
6.5.3 引入外部css样式
1.首先项目要安装样式loader
运行命令 npm i style-loader css-loader -D
2.配置打包处理css样式的第三方loader
在webpack.config.js的module-rules中加入如下配置
{ test: /.css$/, use: ['style-loader','css-loader'] }
3.在src目录下新建一个文件夹css,在新建一个css样式文件cmtlist.css,在css中加入代码如下:
.title{
color: red;
}
4.在页面引入并使用css样式
首先要导入外部样式,然后标签通过指定显示,具体代码如下
//导入样式 import cssObj from '@/css/cmtlist.css' <h1 className='title'>这是评论列表组件</h1>
注意:可以通过通过在css-loader之后,通过?追加参数,这里有个固定的参数modules,表示为css样式启用模块化
{ test: /.css$/, use: ['style-loader','css-loader?modules']} //打包处理css样式的第三方loader
启动模块化样式之后具体引入外部样式为:
<h1 className={cssObj.title}>这是评论列表组件</h1>
注意:css模块化只针对类选择器和id选择器生效
6.5.4 安装外部样式bootstrap
首先安装bootstrap
npm i bootstrap@3.3 -S
然后安装bootstrap所需字体的loader
npm i url-loader -D
在webpack.config.js配置打包字体文件loader
{ test: /.ttf|woff|woff2|eot|svg$/, use: 'url-loader' } //打包处理字体文件loader
最后在页面导入包(注意:在node_modules目录下的包,直接以包名开始引入自己的模块或样式表就可以了)
import bootcss from 'bootstrap/dist/css/bootstrap.css'
在标签中使用
<button className={[bootcss.btn,bootcss['btn-primary']].join(" ")}>按钮</button>
6.5.5 项目启用模块化
规定:第三方样式表都是以.css结尾的,自己的样式表都要以.scss和.less结尾
我们只需要为自己的.scss文件,启用模块化即可
运行下面命令来安装解析scss文件的loader
npm i sass-loader node-sass -D
找到module里的css配置部分,修改正则匹配为:/.(css|scss)$/,在use数组里添加sass-loader
{ test: /.scss$/, use: ['style-loader', 'css-loader?modules','sass-loader'] },
修改css样式表不被模块化
{ test: /.css$/, use: ['style-loader', 'css-loader'] },
然后外部样式引入和使用可直接安装如下写法:
import 'bootstrap/dist/css/bootstrap.css'
<button className="btn btn-primary">按钮</button>
这样就大大增加了开发效率
7.react绑定事件
1)react事件的名称都是由React提供的,因此名称命名必须按照react的规范,如:onClick、onMouseOver
2)为事件提供的处理函数,格式如下:
onClick={ function }
使用案例:(注意:这种情况无法传参,不建议使用)
render(){ return <div> BindEvent组件 <button onClick={this.myClickHandler}>按钮</button> </div> } myClickHandler(){ alert(1) }
另外一种写法,使用箭头函数:(推荐使用)
render(){ return <div> BindEvent组件 <button onClick={()=>this.myClickHandler(this.state.a)}>按钮</button> </div> } myClickHandler=(i)=>{ alert(i) }
8.修改属性
在React中,推荐使用this.setState({ }),修改状态值
myClickHandler = (i) => { this.setState({ msg:"111"+i }) }
注意:
(1)使用setState方法时只会把对应的state状态更新,其他的state状态属性值不变
(2)this.setState方法的执行是异步的
如果调用完this.setState之后,又想立即拿到最新的state值,需要使用this.setState({ },callback)
9.绑定属性
react中没有双向绑定,需要程序员手动进行绑定
首先创建一个input框,值设定为state值
<input type='text' onChange={(e)=>this.txtChanged(e)} style={{ '100%' }} value={this.state.msg} />
实现双向绑定onChange事件
获取值的两种方法:
1.onChange事件传递一个对象e,通过e.target.value来获取
txtChanged=(e)=>{
console.log(e.target.value)
}
2.通过refs来获取,this.refs.txt.value
<input type='text' onChange={()=>this.txtChanged()} ref='txt' style={{ '100%' }} value={this.state.msg} />
txtChanged=()=>{ console.log(this.refs.txt.value) }
3.同步数据
然后将获取最新的值赋值给state,就可以实现数据的双向绑定了
txtChanged=()=>{ const newVal = this.refs.txt.value this.setState({ msg:newVal }) }
10.组件的生命周期
10.1 组件创建阶段
特点:一辈子只执行一次
1)componentWillMount
componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。
2)componentDidMount
组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
10.2 组件运行阶段
特点:根据props属性或者state状态的改变,有选择性的执行0到多次
1)shouldComponentUpdate(nextProps,nextState)
- 主要用于性能优化(部分更新)
- 唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
- 因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
2)componentWillUpdate
shouldComponentUpdate返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。
3)render
4)componentDidUpdate
5)componentWillReceiveProps(nextProps)
- 在接受父组件改变后的props需要重新渲染组件时用到的比较多
- 接受一个参数nextProps
- 通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件
10.3 组件销毁阶段
特点:一辈子只执行一次
1)componentWillUnmount
在此处完成组件的卸载和数据的销毁。
- clear你在组建中所有的setTimeout,setInterval
- 移除所有组建中的监听 removeEventListener