- 原生的JavaScript操作DOM繁琐,效率低(DOM-API操作UI)
- 使用JavaScript,包括jQuery直接操作DOM,浏览器会进行大量的重绘和重排(虽然jQuery简化了操作DOM的步骤,但依然效率低下)
- 原生的JavaScript没有组件化编码方案,代码复用率低
React的定义:用于构建用户界面的JavaScript库。
关键字:
换句话来说,React所做的有三步
- 发送请求获得数据
- 处理数据(过滤,整理格式等)
- 操作DOM呈现页面
也就是说React也可以定义为一个将数据渲染为HTML视图的开源JavaScript库。
命令式编程 VS 声明式编程:
简单来说,命令式编程就是通过代码来告诉计算机去做什么。
而声明式编程是通过代码来告诉计算机你想要做什么,让计算机想出如何去做。
举个生活中的例子就是:
命令式编程:我想喝一个冰可乐,然后我就会对身边的XXX说:“XXX,你去厨房,打开冰箱,拿出一瓶冰可乐,打开之后送过来给我。”
声明式编程:我想喝一个冰可乐,然后我就会对身边的XXX说:“XXX,我想喝冰可乐。”而具体他是怎么拿到的冰可乐,怎么送过来的,是下楼买的还是在冰箱里拿的,我并不关心,我只关心我喝冰可乐的需求是否得到了满足。用代码来举个例子:
如果我要在界面上展示一个按钮,并且点击按钮后会改变该按钮的class。
用DOM编程写的代码就是命令式编程:首先你要指挥浏览器,第一步先要找到id为container的节点,然后创建一个button element,接着给这个button添加一个class name,然后添加一个点击事件,最后将button添加到container节点里。这整个过程每一步操作都是命令式的,一步一步告诉浏览器要做什么。
const container = document. getElementById ( "container" );
const btn = document.createElement ("button");
btn.className = "btn red " ;
btn.textContent = “Demo” ;
btn.onclick = function ( ) {
if ( this.classList.contains ( “red” ) ) {
this.classList.remove( “red” );
this.classList.add ( “blue” );
}else {
this.classList.remove( “blue” );
this.classList.add ( “red” );
}
};
container.appendChild( btn);
而要实现相同功能,采用声明式编程的React就简单得多了。
首先我们定义一个Button组件,在render函数里通过返回一个类似HTML的数据结构,告诉React我要渲染一个Button,它是id为container的子节点。Button上的ClassName是动态变化的,当点击按钮时class要改变,这样就可以了。至于render什么时候被执行,是如何渲染到页面上的,点击按钮之后classname是如何更新的,这些都不需要你关心,你只需要告诉React你希望当前的UI应该是一个什么样的状态就可以了。
class Button extends React. Component {
state = { color: "red" };
handleChange =()=> {
const color = this.state.color == "red" ? "blue" : "red" ;this.setState({ color });
};
render( ) {
return (
<div id="container">
<button
className={ `btn ${this.state.color}` }
onclick={this.handleChange}
>
Demo
</button>
</div>
);
}
}
此外,React使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互,最小化页面重绘
创建虚拟DOM的两种方式
虚拟DOM和真实DOM
React提供一些API来创建一种“特别”的一般js对象
const VDOM = React.createElement('xx',{id:'xx'},'xx')///依次为标签名,标签属性和标签内容
- 1
上面创建的就是一个简单的虚拟DOM对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCawKD9t-1624953908993)(https://i.loli.net/2021/04/25/qwd6egtIJMapLmA.png)]
我们编码时基本只需要操作react的虚拟DOM相关数据,react就会转换为真实的DOM
关于虚拟DOM总结:
- 本质是Object类型的对象(一般对象)
- 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性
- 虚拟DOM对象最终都会被React转换为真实DOM,呈现在页面上
链接:JSX基本语法规则
全称: JavaScript XML
react定义的一种类似于XML的JS扩展语法: JS + XML,本质上还是JavaScript
是**React.createElement(component, props, …children)**方法的语法糖
作用:用来简化创建虚拟DOM
- 写法:var ele =
Hello JSX!
- 它不是字符串(不要加引号),也不是HTML/XML标签
- 它最终产生的就是一个js对象
标签名任意:HTML标签或其他标签
标签属性随意:HTML标签属性或其它
基本语法规则
标签首字母
(1)若小写字母开头,则将该标签转为HTML中同名元素,若HTML中无该标签对应的同名元素,则报错。
(2)若大写字母开头,则react就去渲染对用的组件,若组件没有定义,则报错
标签中的js表达式必须用{ }包含
一定要区分:【JS语句(代码)】与【js表达式】
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式:
- a
- a+b
- demo(1) //函数调用表达式
- arr.map()
- function test() { }
语句(代码):不产生值
下面这些都是语句(代码):
- if(){}
- for(){}
- switch(){case:xxx}
注释需要写在花括号{}中
样式的类名指定不要写class,要写className
内联样式要用style={undefined{key:value}}的形式写第一个{}表示里面是一个js表达式,第二个{}表示里面是一个键值对,里面要写小驼峰的形式, 比如font-size要写成fontSize
myData
虚拟DOM只能有一个根标签,有多个标签时,可用一个div包起来
标签必须闭合
- babel.js的作用
- 浏览器不能直接解析JSX代码, 需要babel转译为纯JS的代码才能运行
- 只要用了JSX,都要加上type=“text/babel”, 声明需要babel来处理
- 模块
- 理解:向外提供特定功能的js程序,一般就是一个js文件
- 为什么要拆成模块:因为随着业务逻辑增加,代码越来越多且复杂
- 作用:服用js,简化js的编写,提高js运行效率
- 组件
- 理解:用来实现局部功能的代码和资源的集合(html/css/js/image等等)
- 为什么一个界面的功能很复杂,不可能写成一整块,要分成一块块写,然后拼起来
- 作用:复用编码,简化项目编码,提高运行效率
- 模块化
当一个应用的js都是以模块来编写,这个应用就是一个模块化的应用
- 组件化
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
组件的类型
注意:
(1)组件名必须首字母大写
(2)虚拟DOM元素只能有一个根元素
(3)虚拟DOM元素必须有结束标签
简单组件:无状态的组件
复杂组件:有状态(state)的组件
状态:举例子说
- 人是有状态的,比如今天的精神如何,人的状态会影响人的行为
- 组件也是有状态的,组件的状态驱动页面,数据放在状态里
内部读取某个属性值: this.props.name
对props中的属性值进行类型限制和必要性限制
第一种方式(React v15.5 开始已弃用):
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number
}
第二种方式(新):使用prop-types库进限制(需要引入prop-types库)
Person.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number.
}
扩展属性:将对象的所有属性通过props传递:<Person {…person}/>
默认属性值:
Person.defaultProps = {
age: 18,
sex:'男'
}
组件类的构造函数
constructor(props){
super(props)
console.log(props)//打印所有属性
}
理解:组件内的标签可以定义ref属性来标识自己
编码
字符串形式的ref:(已经不被react推荐使用)官方说明
回调形式的ref
{this.input1 = c}}
createRef创建ref容器
myRef = React.createRef() ;
通过onXxx属性指定处理函数(注意大小写,与原生的js区分开)
a) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 ——目的是为了更好的兼容性
b) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ——目的是为了高效
通过event.target得到发生事件的DOM元素对象 ——为了避免过度使用ref
不要过度使用ref,当发生事件的DOM正好是要操作的DOM元素时可以用event.target的形式
非受控组件:现用现取(ref)
受控组件:随着输入维护状态为受控组件(onChange , setState)
需求:定义组件实现以下功能:
挂载:mount。当 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。
卸载:unmount。同时,当 DOM 中 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”
生命周期的三个阶段(旧)
初始化阶段: 由ReactDOM.render()触发—初次渲染
1). constructor()
2). componentWillMount()
3). render()
4). componentDidMount() = = = =>常用,一般在这个钩子中做一些初始化的事,例如开启定时器、 发送网络请求、订阅消息、开启监听, 发送ajax请求等
更新阶段: 由组件内部this.setSate()或父组件重新render触发
1). shouldComponentUpdate()
2). componentWillUpdate()
3). render() = = = =>必须要使用
4). componentDidUpdate()
卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1). componentWillUnmount() = = = =>常用,一般在这个钩子做一些收尾的工作,例如,关闭定时 器、取消订阅消息
生命周期的三个阶段(新)
**1. ** 初始化阶段: 由ReactDOM.render()触发—初次渲染
constructor()
getDerivedStateFromProps(新增,很少用,上官网了解即可)
(此方法适用于罕见的用例,即 state 的值在任何时候都取决于 props)
render()
componentDidMount()
更新阶段: 由组件内部this.setSate()或父组件重新render触发
getDerivedStateFromProps
shouldComponentUpdate()
render()
**getSnapshotBeforeUpdate ** 在更新之前获取快照,有点实用意义
componentDidUpdate()
**3. ** 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
componentWillUnmount()
16版本能正常使用,17版本使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。
经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?
虚拟DOM中key的作用:
1). 简单地说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。
2). 详细地·说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
(1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
(2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
根据数据创建新的真实DOM,随后渲染到到页面
- 用index作为key可能会引发的问题:
1) 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
2.)如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题。
3) ==注意!==如果不存在对数据的逆序添加、逆序删除等破坏顺序操作, 仅用于渲染列表用于展 示,使用index作为key是没有问题的。
3. 开发中如何选择key?:
1) 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
2) 如果确定只是简单的展示数据,用index也是可以的。
xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目
1)包含了所有需要的配置(语法检查、jsx编译、devServer…)
2) 下载好了所有相关的依赖、
3)可以直接运行一个简单效果
react提供了一个用于创建react项目的脚手架库: create-react-app
项目的整体技术架构为: react + webpack + es6 + eslint
使用脚手架开发的项目的特点: 模块化, 组件化, 工程化
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标(一定要是icon格式)
index.html -------- 主页面(整个项目只有这一个html文件,SPA应用,即单页面应用)
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
App.css -------- App组件的样式
App.js --------- App 组件
App.test.js ---- 用于给App做测试(几乎不用)
index.css ------ 样式
index.js ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js
— 页面性能分析文件(需要web-vitals库的支持)
setupTests.js
---- 组件单元测试的文件(需要jest-dom库的支持)
拆分组件: 拆分界面,抽取组件
实现静态组件: 使用组件实现静态页面效果
实现动态组件
3.1 动态显示初始化数据
3.1.1 数据类型
3.1.2 数据名称
3.1.3 保存在哪个组件?
3.2 交互(从绑定事件监听开始)
React本身只关注于界面, 并不包含发送ajax请求的代码
前端应用需要通过ajax请求与后台进行交互(json数据)
react应用中需要集成第三方ajax库(或自己封装)
Query: 比较重, 如果需要另外引入不建议使用
axios: 轻量级, 建议使用
1) 封装XmlHttpRequest对象的ajax
2) promise风格
3) 可以用在浏览器端和node服务器端
GET请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response.data);
})
.catch(function (error) {
console.log(error);
});
axios.get(’/user’, {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
POST请求
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
方法1
在package.json中追加如下配置
"proxy":"http://localhost:5000"
说明:
1)优点:配置简单,前端请求资源时可以不加任何前缀。
2)缺点:不能配置多个代理。
3)工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法2:
1)第一步:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
2) 编写setupProxy.js配置具体代理规则:
const proxy = require('http-proxy-middleware')
module.exports = function(app) {
app.use(
proxy(’/api1’, { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
target: ‘http://localhost:5000’, //配置转发目标地址(能返回数据的服务器地址)
changeOrigin: true, //控制服务器接收到的请求头中host字段的值
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
changeOrigin默认值为false,但我们一般将changeOrigin值设为true
*/
pathRewrite: { ’^/api1’: ‘’} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
}),
proxy(’/api2’, {
target: ‘http://localhost:5001’,
changeOrigin: true,
pathRewrite: { ’^/api2’: ‘’}
})
)
}
说明:
工具库: PubSubJS
下载: npm install pubsub-js --save
使用:
1) import PubSub from ‘pubsub-js’ //引入
2) PubSub.subscribe(‘delete’, function(data){ }); //订阅
3) PubSub.publish(‘delete’, data) //发布消息
理解
1)先订阅,再发布(理解:有一种隔空对话的感觉)
2)适用于任意组件间通信
3)要在组件的componentWillUnmount中取消订阅
文档
1) https://github.github.io/fetch/
2) https://segmentfault.com/a/1190000003810652
特点
1)fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求(axios和jQuery都是对XmlHttpRequset的封装)
2)老版本浏览器可能不支持
相关API
1)GET请求
fetch(url).then(function(response) {
return response.json()
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
});
2)POST请求
fetch(url, {
method: "POST",
body: JSON.stringify(data),
}).then(function(data) {
console.log(data)
}).catch(function(e) {
console.log(e)
})
单页Web应用(single page web application,SPA)。
整个应用只有==一个完整的页面==。
点击页面中的链接==不会刷新页面,只会做页面的局部更新。==
数据都需要通过ajax请求获取, 并在前端异步展现。
1. 什么是路由?
一个路由就是一个映射关系(key:value)
key为路径, value可能是function或component
2. 路由分类
1) 后端路由:
理解: value是function, 用来处理客户端提交的请求。
注册路由: router.get(path, function(req, res))
工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
2) 前端路由:
浏览器端路由,value是component,用于展示页面内容。
注册路由:
工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件
react的一个插件库。
专门用来实现一个SPA应用。
基于react的项目基本都会用到此库。
< BrowserRouter >
< HashRouter >
< Route >
< Redirect >
< Link >
< NavLink >
< Switch >
history对象
match对象
withRouter函数
里面由笔记的markdown版本和源代码,还有一些其他的学习笔记
GitHub地址
尚硅谷React全家桶
ps:张天禹老师讲得超级棒,yysd