ajax请求
1.请求基本步骤
<body>
<form action="###">
手机号:<input type="text" name="phone" placeholder="手机号" id="phone">
<br>
密码:<input type="text" name="pass" id="pass">
<br>
<button id="btn">登录</button>
</form>
<script>
const phone=document.getElementById('phone')
const pass=document.getElementById('pass')
const btn=document.getElementById('btn')
btn.onclick=function(){
//1.1利用ajax核心对象xmlHttpRequest创造实例对象
//1.2 利用open方法打开路径
//1.3 利用send方法发送请求参数
//1.4 利用readystatechange事件监听readyState请求状态码的变化
return false
}
</script>
</body>
1.1 利用ajax核心对象xmlHttpRequest创造实例对象
因为XMLHttpRequest的原型对象中包含很多方法用来支持ajax请求
const xhr=new XMLHttpRequest()
注意:在与form表单标签使用时,要注意form表单的默认请求事件 return false
1.2 利用open方法打开路径
open的3个参数:
请求方式:get/post
请求地址:
1、get请求的地址中包含查询字符段(请求参数)因为get请求默认读取缓存,所以可以用时间戳拼接到查询字符串后面,保证请求地址不同
2、post请求参数在请求体中
是否异步:false/true
//get请求
xhr.open('get',`/login?phone=${phone.value}&pass=${pass.value}&_=${Date.now()}`,true)
//post请求
xhr.open('post','/login',true)
1.3 利用send方法发送请求参数
send 参数:
1.如果是get请求,因为请求参数都在请求地址里面,所以可以不填参数,也可以填写null
2.如果是post请求,里面填写的是要发送的请求体
send发送数据的时候需要设置请求头,告诉服务端我发送的数据是什么类型
- 如果send中数据格式是url查询字符串格式,请求头的content-type :application/x-www-form-urlencoded
- 如果send中数据格式是json字符串格式,请求头的content-type:application/json
//get请求
xhr.send() 或 xhr.send(null)
//post请求
xhr.setRequestHeader('Content-Type','application/json;charset=utf-8')
xhr.send(JSON.stringfy{
phone:phone.value,
pass:pass.value
})
1.4 利用readystatechange事件监听readyState请求状态码的变化
readyState请求状态码(0-4):
0 : 表示没有发送
1 : 表示启动了open方法
2 : 表示启动了send方法
3 : 表示开始部分接受数据
4 : 表示成功了
status 响应状态码(200-299)
responseText 接受其他文本类响应(json)
responseXml 接受xml响应
xhr.onreadystatechange=function(){
console.log('readyState'+xhr.readyState)
if(xhr.readyState===4&&xhr.status>=200&&xhr.status<=299){
console.log('请求发送成功了')
const resData=JSON.parse(xhr.responseText)
//app.js 设置了响应成功code :200
if(resData.code===200){
return alert("登录成功,您的用户名是" + resData.data.username)
}
alert('登录失败')
}
2.jQuery中ajax
2.1 一级封装
$(function(){
$('#btn').click(function(){
$.ajax({
url:'/login',//请求地址
type:'get' /'post' , //请求方式
cache:false,//是否缓存
data:{
//请求参数
//数据:如果是get请求 则帮我们拼接在url上,如果是post请求 会转成字符串
phone:$('#phone').value,
pass:$('#pass').value
},
dataType: "json", //预期的响应数据的格式
headers: {}, //请求头
timeout: 2000, //设置超时时间
sucess(val){
//成功的回调函数,参数就是得到的数据
console.log(val)
}
})
return false
})
})
2.2 二级封装
$(function(){
$('#btn').click(function(){
//get请求
$.get('/login',
{
//请求参数
//数据:如果是get请求 则帮我们拼接在url上,如果是post请求 会转成字符串
phone:$('#phone').value,
pass:$('#pass').value
},
(val)=>{
//成功的回调函数,参数就是得到的数据
console.log(val)
},
'json')
//post请求
$.post('/login',
{
//请求参数
//数据:如果是get请求 则帮我们拼接在url上,如果是post请求 会转成字符串
phone:$('#phone').value,
pass:$('#pass').value
},
{
//请求参数
//数据:如果是get请求 则帮我们拼接在url上,如果是post请求 会转成字符串
phone:$('#phone').value,
pass:$('#pass').value
},
(val)=>{
//成功的回调函数,参数就是得到的数据
console.log(val)
},
'json')
return false
})
})
2.3 三级封装
$(function(){
$('#btn').click(function(){
$.getJSON('/login',
{ //请求参数
phone:$('#phone').value,
pass:$('#pass').value
},
(val)=>{
console.log(val)
})
})
})
Promise
1. Promise基本介绍
1.1 Promise构造函数
-
Promise对象是一个异步编程的解决方案,可以将异步代码操作以同步的流程表达出来,避免了层层嵌套的回调函数(俗称‘回调地狱’)
-
Promise上有then,catch,finally三个原型方法
-
Promise上有all,allSettled,race,any,reject,resolve 六个静态方法
-
Promise是处理异步代码的,本身Promise自己的回调函数的执行是同步的
1.2 Promise基本使用
-
Promise实例化的接收一个回调函数作为参数
-
Promise回调函数接受两个参数分别是reslove和reject(这两个都是函数)
-
Promise中书写异步代码,在异步代码成功的时候,可以调用reslove函数,在异步代码失败的时候可以调用reject函数
-
Promise的返回值会根据resolve和reject的调用决定状态,如果没有reject或resolve,则会返回pending状态的promise实例对象,如果是同步代码错误,则直接返回失败的实例对象promise,值为错误信息
resolve和reject接受参数,分别是成功的值和失败的值,会传递给Promise的实例对象 -
Promise实例对象的状态只能由pending变为其他状态
1.3 Promise实例化对象
-
promiseState属性:当前promise的状态
-
默认是pending状态,代表异步代码正在执行中
-
fulfilled/resolved:代表异步代码执行成功并调用了resolve函数
-
rejected: 代表异步代码执行失败并调用了reject函数
-
-
PromiseResult属性:当前promise执行的结果
-
如果异步代码执行成功,值是resolve函数中传递值
-
如果异步代码执行失败,值是reject函数中的错误信息
-
2.Promise原型方法
const p1=new Promise((resolve,reject)=>{
try{
throw new Error('A有错误')
setTimeout(()=>{
throw new Error('A异步有错误')
console.log('A打印成功')
resolve('A')
})
}catch(e){
reject(e.message)
}
})
注意:try...catch...只能捕获同步错误,异步里面的错误不能到catch里面,会直接报错
同时同步代码里面有错误后,同步代码后面的代码不会执行,会直接进入catch里面
try...catch...捕获错误后,就不会直接报错 就是会进入catch,我们可以通过error.message获取错误信息,但是不会直接直接在页面里报错
2.1 then()方法
p1.then((value)=>{
//若调用then方法的promise实例状态为成功,则进入这个回调函数,value是promise实例的值
console.log(value) //A
},(reason)=>{
//若调用then方法的promise实例状态为失败,则进入这个回调函数,reason是promise实例的传入的错误信息
console.log(reason)//e.message的具体信息
})
2.1.1 then方法基本介绍
- then方法是供Promise的实例对象调用,能够通过实例对象的状态执行下一步操作
- then方法可以接受2个参数,都是回调函数,分别处理成功状态和失败状态的的promise实例对象
- then方法中回调函数都是异步进行的
- then方法中回调函数的参数就是成功或者失败的promise实例的
2.1.2 then方法返回值
- 默认返回一个成功状态的promise对象,值为返回的值
- 如果then中出现报错且没有解决,则返回一个失败状态的promise对象,值为错误信息
- 如果then返回一个promise对象,则返回值与这个promise对象挂钩
- 如果then中没有处理调用then方法的promise实例对应状态的回调函数时候,会发生值穿透现象
(值穿透现象:返回一个promise对象,promise对象的内容和调用方法的promise实例一样)
2.2 catch()方法
p1.catch(reson=>{
//catch只处理调用该方法的失败状态下的promise实例,如果是成功状态的promie实例,会发生值的穿透
//若调用then方法的promise实例状态为失败,则进入这个回调函数,reason是promise实例的传入的错误信息
console.log(reson)//e.message的具体信息
})
2.2.1 catch方法基本介绍
- 和then的第二个回调函数作用一致
- 不能和then的第二个回调函数同时使用
2.2.2 catch返回值:规律与then方法一致
2.3 finally()方法
p1.finally(()=>{
//调用的promise实例状态为成功或失败都可以进入这个回调函数
console.log(reson)//undefined
console.log(value)//undefined
})
2.3.1 finally方法基本介绍
- 无论调用方法的promise实例状态是成功与否,都会进入finally的回调函数
- finally的回调函数不接受任何参数
2.3.2 finally返回值:
-
默认情况下直接穿透
-
如果finally中出现异常,则会返回一个失败的promise对象,值为异常信息
-
如果finally中返回promise实例
- 返回的promise实例为成功,则无需理会,直接穿透
- 返回的promise实例为失败,则finally返回这个失败的promise实例
2.4 案例练习
3.Promise静态方法
const p1 = new Promise((resolve, reject) => {
console.log("开始请求A");
try {
// throw new Error("A错误")
setTimeout(() => {
console.log("A成功");
resolve("A")
}, 2000)
} catch (e) {
reject(e.message)
}
})
const p2 = new Promise((resolve, reject) => {
console.log("开始请求B");
try {
// throw new Error("B错误")
setTimeout(() => {
console.log("B成功");
resolve("B")
}, 1000)
} catch (e) {
reject(e.message)
}
})
const p3 = new Promise((resolve, reject) => {
console.log("开始请求C");
try {
// throw new Error("C错误")
setTimeout(() => {
console.log("C成功");
resolve("C")
}, 1500)
} catch (e) {
reject(e.message)
}
})
const allResult = Promise.all([p1, p2, p3]);//传入的参数必须是iterable
const allResult = Promise.allSettled([p1, p2, p3]);
const allResult = Promise.race([p1, p2, p3]);
const allResult = Promise.any([p1, p2, p3]);
const allResult = Promise.resolve([p1, p2, p3]);
const allResult = Promise.reject([p1, p2, p3]);
console.log("allResult", allResult);
3.1 all()方法
- 返回一个promise对象
- 如果参数中promise对象状态都是成功的,则返回一个成功的promise对象,值为参数中所有promise对象的值组成的数组
- 如果参数中有任何一个promise对象状态是失败的,则返回一个失败的promise对象,值为参数中监听到的最先执行失败的promise对象的错误信息(reject中的值)
3.2 allSettled()方法
- 返回一个promise对象
- 主要是为了检测所有的promise对象是否执行结束(无论成功还是失败)
- allSettled方法永远返回成功的promise状态,值是一个数组,数组的每一个值是监听的promise对象的状态和值组成的新对象
3.3 race()方法
- 返回一个promise对象
- 返回的promise对象与参数中第一个改变状态的promise对象挂钩
3.4 any()方法
- 返回一个promise对象
- 返回的promise对象与参数中执行最快且成功promise对象挂钩
- 如果参数中所有promise对象的状态都为失败,返回一个错误状态的promise对象,值为新的错误类型AggregateError:All promises were rejected
3.5 resolve()方法
- 快速的得到一个成功的promise对象,值为resolve的参数(也可以快速的把一个值包装到成功promise对象中,就可以继续then异步处理)
- 当resolve的参数是一个promise对象的时候,resolve的返回值就和这个promise对象挂钩
3.6 reject()方法
- 快速的得到一个失败的promise对象,值为reject的参数(也可以快速的把一个错误包装到失败promise对象中,就 可以继续catch异步处理)
- 当reject的参数是一个promise对象的时候,返回一个失败状态的promise对象,值为传入的参数promise对象
async&await
1.generator
// generator函数 *号 yield关键字
function* fn() {
console.log("123");
yield console.log("1");
// 一个yield分号结束之前,都属于yield
yield console.log("2");
yield console.log("3");
yield console.log("4");
yield console.log("5");
}
const res = fn();
console.log(res); //res中的原型链上有next方法 返回一个迭代器对象(iterator)
document.onclick = function () {
res.next();
};
function* getData() {
yield setTimeout(() => {
console.log("要1");
res.next();
}, 2000);
yield setTimeout(() => {
console.log("要2");
res.next();
}, 1000);
yield setTimeout(() => {
console.log("要3");
res.next();
}, 1500);
yield setTimeout(() => {
console.log("要4");
res.next();
}, 1500);
yield setTimeout(() => {
console.log("要5");
}, 1000);
}
const res = getData(); //先调用generator函数 得到一个对象
document.onclick = function () {
// 点击文档后开始调用getData 每次点击会重新调用,重新开始
// const res = getData();
res.next();
};
2.async&await
// 最终结果顺序:1 3 2
async function fn(){
await console.log(1);
// await语法语句后面的代码是异步代码
console.log(2);
}
fn()
console.log(3);
// 最终结果顺序:3 2 1
async function fn() {
// await后面的代码 没有等待前面计时器异步代码的执行
await setTimeout(() => {
console.log(1);
}, 2000);
// await语法语句后面的代码是异步代码
console.log(2);
}
fn();
console.log(3);
// 结果顺序:3 1 2
async function fn() {
// await 后面等待promise对象 因为promise对象有异步代码成功结束节点提示
// 如果接受到的是reject失败结果,await语句后面的代码不会执行 会报错
// 如果接受到的是resolve成功结果,await语句后面代码正常执行,await返回值是promise实例对象的值(resolve传的值)
const p=await new Promise((resolve, reject) => {
setTimeout(() => {
console.log(1);
resolve('这是异步代码结果');
reject()
}, 2000);
});
console.log(p);//这是异步代码结果
console.log(2);
}
fn();
console.log(3);
async函数返回值
- async函数返回一个promise对象
- 默认返回成功状态的promise对象,值为函数返回值(与await没有关系)
- 当async函数内部出现错误时,返回失败状态的promise对象,值为错误信息
- 当async函数内的await等到了一个失败状态的promise实例对象,则函数返回值与这个promise对象挂钩
- await返回值:成功的promise对象的值
async function getA() {
// throw new error('错啦')
const p=await new Promise((resolve, reject) => {
// throw new Error('错啦')
try {
console.log("开始请求A");
// throw new Error("A错啦");
setTimeout(() => {
console.log("A开始执行");
// throw new Error("A错啦");
console.log("A成功啦");
resolve("A成功");
}, 1000);
} catch (e) {
reject(e.message);
}
});
console.log('B');
console.log('p',p);
return 1;
}
const res = getA();
console.log("async函数的返回值", res);
3.案例练习
3.1 getData案例
3.1.1 普通版
async function getData(){
console.log('开始请求A数据');
const A=await new Promise((resolve,reject)=>{
try{
setTimeout(()=>{
console.log("A成功了");
resolve({
name:'laowang'
})
})
}catch(e){
reject(e.message)
}
})
console.log('开始请求B数据');
const B=await new Promise((resolve,reject)=>{
try{
setTimeout(()=>{
console.log("B成功了");
resolve({
age:18
})
})
}catch(e){
reject(e)
}
})
console.log('开始请求C数据');
const C=await new Promise((resolve,reject)=>{
try{
setTimeout(()=>{
console.log("C成功了");
resolve(Object.assign(A,B,{gender:'nan'}))
})
}catch(e){
reject(e)
}
})
console.log(C)
}
getData()
3.1.2 函数封装版
function getName() {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
resolve({
name: "zs",
});
}, 2000);
} catch (e) {
reject(e.message);
}
});
}
function getAge() {
return new Promise((resolve, reject) => {
try {
setTimeout(() => {
resolve({
age: 18,
});
}, 1500);
} catch (e) {
reject(e.message);
}
});
}
function getGender(name, age) {
return new Promise((resolve,reject) => {
try {
setTimeout(() => {
resolve(Object.assign(name, age, { gender: "男" }));
}, 1000);
} catch (e) {
reject(e.message);
}
});
}
async function getData() {
const name = await getName();
const age = await getAge();
const data = await getGender(name, age);
console.log(data);
}
getData()
3.2 写入文件案例
3.2.1 普通版
const fs = require("fs");
const path = require("path");
const filePath = path.join(__dirname, "./a.txt");
async function writeWordsInTxt() {
const fd = await new Promise((resolve, reject) => {
fs.open(filePath, "a", (err, fd) => {
if (err) return reject(err.message);
resolve(fd);
});
});
await new Promise((resolve, reject) => {
fs.write(fd, "你好呀", (err) => {
if (err) return reject(err.message);
resolve();
});
});
await new Promise((resolve, reject) => {
fs.close(fd, (err) => {
if (err) return reject(err.message);
resolve();
});
});
}
writeWordsInTxt()
3.2.2 函数封装版
const fs = require("fs");
const path = require("path");
const filePath = path.join(__dirname, "./a.txt");
function open() {
return new Promise((resolve, reject) => {
fs.open(filePath, "a", (err, fd) => {
if (err) return reject(err.message);
resolve(fd);
});
});
}
function write(fd) {
return new Promise((resolve, reject) => {
fs.write(fd, "封装好啦", (err) => {
if (err) return reject(err.message);
resolve();
});
});
}
function close(fd) {
return new Promise((resolve, reject) => {
fs.close(fd, (err) => {
if (err) return reject(err.message);
resolve;
});
});
}
async function writeWordsInFile() {
const fd = await open();
await write(fd);
close(fd);
}
writeWordsInFile();
3.2.3 promisify版
const fs = require("fs");
const path = require("path");
const filePath = path.join(__dirname, "./a.txt");
const { promisify } = require("util");
const open = promisify(fs.open);
const write = promisify(fs.write);
const close = promisify(fs.close);
(async function () {
const fd = await open(filePath, "a");
await write(fd,'promisify完成啦');
close(fd);
})();
axios
1.axios基础请求
使用:
- 用
<button id="get">get-得到用户信息</button>
<button id="post">post-得到用户信息</button>
<button id="put">put-得到用户信息</button>
<button id="del">delete-删除用户信息</button>
<script>
const oGet = document.getElementById("get");
const oPost = document.getElementById("post");
const oPut = document.getElementById("put");
const oDel = document.getElementById("del");
// get请求
oGet.onclick =async function () {
try{
const re=await axios({
url: "/user",
method: "GET",
// get请求发送的数据 1.拼接到url上 2.使用params配置
// params有2种格式 1.对象格式 2.查询字符串格式
params:{
userID:001
},
timeout:1000,
});
console.log('get',re);
}catch(e){
console.log(e.message);
}
};
// post请求
oPost.onclick = async function () {
try{
const re=await axios({
url: "/login",
method: "POST",
// post请求 请求体参数用data发送,data是一个对象类型
data:{
phone:'1300000001',
pass:'11111'
},
timeout:1000,
});
console.log('post',re);
}catch(e){
console.log(e.message);
}
};
// put请求
oPut.onclick = async function () {
try{
const re=await axios({
url: "/update",
method: "put",
//put请求同post请求一样
data:{
userID:'zs',
age:18
},
timeout:1000,
});
console.log('put',re);
}catch(e){
console.log(e.message);
}
};
// delete请求
oDel.onclick = async function () {
try{
const re=await axios({
url: "/deleteuser",
method: "Delete",
// delete请求同get请求一样
params:{
userID:'zs',
},
timeout:1000,
});
console.log('del',re);
}catch(e){
console.log(e.message);
}
};
</script>
2.axios别名请求
<button id="get">get-得到用户信息</button>
<button id="post">post-得到用户信息</button>
<button id="put">put-得到用户信息</button>
<button id="del">delete-删除用户信息</button>
<script>
const oGet = document.getElementById("get");
const oPost = document.getElementById("post");
const oPut = document.getElementById("put");
const oDel = document.getElementById("del");
// get请求
oGet.onclick = async function () {
try {
const re = await axios.get("/user", {
params: {
userID: 001,
},
timeout: 1000,
});
// 将查询字符串写在请求地址上
// const re=await axio.get('/user?userid=001',{
// timeout: 1000,
// })
console.log("get", re);
} catch (e) {
console.log(e.message);
}
};
// post请求
oPost.onclick = async function () {
try {
const re = await axios.post("/login", {
data: {
phone: "1300000001",
pass: "11111",
},
timeout: 1000,
});
console.log("post", re);
} catch (e) {
console.log(e.message);
}
};
// put请求
oPut.onclick = async function () {
try {
const re = await axios.put("/update", {
data: {
userID: "zs",
age: 18,
},
timeout: 1000,
});
console.log("put", re);
} catch (e) {
console.log(e.message);
}
};
// delete请求
oDel.onclick = async function () {
try {
// const re = await axios.delete("/deleteuser", {
// params: {
// userID: "zs",
// },
// timeout: 1000,
// });
const re=await axios.delete('/deleteuser?userid=zs',{
timeout: 1000,
})
console.log("del", re);
} catch (e) {
console.log(e.message);
}
};
</script>
3.axios配置默认值
// axios全局默认配置(配置默认基础路径)
axios.defaults.baseURL = "/api";
4.创建axios实例
const oGet = document.getElementById("get");
const oPost = document.getElementById("post");
const oPut = document.getElementById("put");
const oDel = document.getElementById("del");
// axios全局默认配置(配置默认基础路径)
// axios.defaults.baseURL = "/api";
// 创建实例 (创建一个副本)
// const myReq = axios.create();
// 创建实例,并传入配置项
const myReq = axios.create({
// 配置当前实例的基础路径
baseURL: "/api",
// 配置当前实际的超时时间
timeout: 1000,
// 配置当前实例的请求头
headers: {
"hello": "world",
},
});
// 创造另一个实例,并传入配置项
const yourReq = axios.create({
// 配置当前实例的基础路径
baseURL: "/sss",
// 配置当前实际的超时时间
timeout: 1000,
// 配置当前实例的请求头
headers: {
"isAction": "no",
},
});
const oGet = document.getElementById("get");
const oPost = document.getElementById("post");
const oPut = document.getElementById("put");
const oDel = document.getElementById("del");
// axios全局默认配置(配置默认基础路径)
// axios.defaults.baseURL = "/api";
// 创建实例 (创建一个副本)
// const myReq = axios.create();
// 创建实例,并传入配置项
const myReq = axios.create({
// 配置当前实例的基础路径
baseURL: "/api",
// 配置当前实际的超时时间
timeout: 1000,
// 配置当前实例的请求头
headers: {
"hello": "world",
},
});
// 创造另一个实例,并传入配置项
const yourReq = axios.create({
// 配置当前实例的基础路径
baseURL: "/sss",
// 配置当前实际的超时时间
timeout: 1000,
// 配置当前实例的请求头
headers: {
"isAction": "no",
},
});
// get请求
oGet.onclick = async function () {
try {
const re = await myReq.get("/user", {
params: {
userID: 001,
},
});
console.log("get", re);
} catch (e) {
console.log(e.message);
}
};
5.axios拦截器
// 配置请求拦截器
myReq.interceptors.request.use(
(config) => {
// 请求拦截器中拦截的是axios请求的配置项
console.log("请求配置项", config);
// 拦截配置项后,要把配置项return出去,否则请求无法进行
return config;
},
(error) => {
// 进入这个函数,说明请求未到达服务器,请求就有错,此时可以放回一个失败的promise对象,同时错误原因也返回出去,await等到这个失败的promise对象,可以知道请求有问题
return Promise.reject(error);
}
);
// 配置响应拦截器
myReq.interceptors.response.use(
(response) => {
console.log("response", response);
return response;
},
(error) => {
//如果请求出现错误,则我们希望进入发请求的时候catch的异常处理
//在发请求阶段得到响应后异常处理怎么进去呢?await等的时候失败的promise对象
//所以我们可以把error包装成一个失败的promise对象,返回出去交给请求的地方处理
console.log("响应拦截器失败处理函数");
// 进入这个函数,说明请求到达服务器,回来的时候发现有错,返回这个失败的promise对象,可以知道请求有问题
return Promise.reject(error);
}
);
6.axios拦截器的应用
// 配置请求拦截器
myReq.interceptors.request.use(
(config) => {
// 请求拦截器中拦截的是axios请求的配置项
console.log("请求配置项", config);
// 应用1:此处进度条开始
NProgress.start()
return config;
},
(error) => {
// 响应有问题,进度条也可以结束
NProgress.done()
// 进入这个函数,说明请求未到达服务器,请求就有错,此时可以放回一个失败的promise对象,同时错误原因也返回出去,await等到这个失败的promise对象,可以知道请求有问题
return Promise.reject(error);
}
);
// 配置响应拦截器
myReq.interceptors.response.use(
(response) => {
console.log("response", response); //response是个对象 其中data是response回来的数据
// 1.响应拦截器用法1:进度条执行结束 NProgress
// 此处进度条结束
NProgress.done()
// 应用2:对响应的数据进行处理 server可以根据我们传过去的信息判断符不符合要求,response根据符不符合要求返回对应状态 我们可以通过判断状态 看是否提供对应页面
//比如登录-无论登录成功还是失败,只要把结果响应回来了都算响应成功
//但是对于我们来说,只有登录成功才叫响应成功,如果登录失败,我们就把他交给catch处理
// 拦截配置项后,要把配置项return出去,否则请求无法进行
if(response.data.code !== 200){
return Promise.reject({message:response.data.msg})
}
return response.data;
},
(error) => {
//如果请求出现错误,则我们希望进入发请求的时候catch的异常处理
//在发请求阶段得到响应后异常处理怎么进去呢?await等的时候失败的promise对象
//所以我们可以把error包装成一个失败的promise对象,返回出去交给请求的地方处理
console.log("响应拦截器失败处理函数");
// 进入这个函数,说明请求到达服务器,回来的时候发现有错,返回这个失败的promise对象,可以知道请求有问题
return Promise.reject(error);
}
);
7.axios取消请求
// 取消axios请求 首先要拿到cancelToken构造函数,cancelToken构造函数在axios上
// 注:cancelToken构造函数只能在axios拿 不能从axios实例上拿
const CancelToken = axios.CancelToken;
// 定义一个全局变量,用来保存局部中得等到取消请求的函数
let cancel=null;
oCancel.onclick=function(){
// 启动cancel函数
// 函数里可以传入参数 请求理由
cancel()
}
跨域
1.JSONP
<button id="get">get-得到用户信息</button>
<script>
const oGet = document.getElementById('get');
//回调函数,用来jsonp发请求的时候 携带回调函数给服务端 用来服务端调用
function callback(value) {
alert("我是callback:" + value)
}
let oScript = null;
//get请求
oGet.onclick = async function () {
if (oScript) {
oScript.remove()
}
//因为跨域原因无法直接请求,但是script标签的src可以跨域,我们选择创建一个script标签,帮助我们发送请求
oScript = document.createElement("script");
//把请求地址赋值给script标签的src属性
oScript.src = "http://192.168.20.82:8888/user?userID=001&cb=callback";
//把script标签插入页面中,则直接会把请求发送
document.body.appendChild(oScript)
}
</script>
//服务器部分
app.get("/user", (req, res) => {
const {
userID,
//拿到客户端发送的回调函数名称
cb
} = req.query;
console.log("请求进来了", userID);
//模拟客户端需要的数据
const userToken = "abcdefghijk";
//设置响应的数据类型是js类型
res.set("Content-Type", "application/javascript;charset=utf-8")
//给客户端的script标签响应一个js代码(js代码是调用了客户端发来的回调函数,并传入了数据作为实参)
return res.send(`${cb}('${userToken}')`);
})
2.cors
//get请求
oGet.onclick = async function () {
try {
//get请求参数放在配置中写
const re = await axios.get("http://192.168.20.82:8888/user", {
params: {
userID: "001"
}
})
console.log("最终的数据是1", re);
} catch (e) {
console.log("请求出现异常,异常信息是", e);
}
}
//服务器部分
app.get("/user", (req, res) => {
const {
userID
} = req.query;
//获取当前的origin地址
const nowOrigin = req.headers.origin;
//设置白名单
const allowOrigin = ["http://127.0.0.1:5501", "http://127.0.0.1:5505", "http://127.0.0.1:5500", "http://127.0.0.1:5504"]
//判断当前的origin地址是否在白名单中 如果在 则设置跨域
if (allowOrigin.includes(nowOrigin)) {
//设置cors跨域
res.set("Access-Control-Allow-Origin", nowOrigin)
}
if (userID === "001") {
return res.json({
code: 200,
msg: "ok",
data: {
username: "laoli"
}
});
}
res.json({
code: 201,
msg: "用户id出错",
data: null
})
})
3.代理
缓存
1.强制缓存
强制缓存:
-
强制缓存是向浏览器缓存查找请求结果,并根据请求结果来决定我们是否可以使用缓存的过程
-
简单来讲,就是浏览器直接使用自己的缓存,不进行任何的请求
-
强制缓存的设置过程
-
客户端请求的时候,需要携带 Cache-Control请求头字段,值是 max-age=XXXX(秒数)
-
服务端响应的时候,也需要携带 Cache-Contorl的响应头字段,值是max-age=XXXX(秒数)
-
当下次再次请求的时候,判断自己是否符合强制缓存条件,如果符合,则直接读取缓存,如果不符合,则会走协商缓存
-
///服务器部分
app.get("/img", (req, res) => {
const filePath = path.resolve(__dirname, "./01.jpg");
const rs = fs.createReadStream(filePath);
res.set("Cache-Control", "max-age=1000")
rs.pipe(res)
})
2.协商缓存
协商缓存:
- 客户端向服务端发送请求,请求某一个资源文件
- 服务端向客户端响应当前的文件,并且在响应头中添加两个字段,分别是文件的唯一标识(eTag)和当前被请求文件的最后一次修改时间(last-modified)
- 客户端接收到响应,还要处理关于协商缓存的信息,把文件的唯一标识和最后一次修改时间保存下来,并且还要修改字段名,把eTag更名为if-none-match,把last-modified更名为if-modified-since
- 客户端再次请求资源,会携带if-none-match和if-modified-since字段
- 服务端接收到请求后,会把if-none-match和自己的eTag进行比较,把if-modified-since和自己的last-modified进行比较,如果都相同,则直接响应304状态码,要求读取缓存。否则响应数据,并携带最新的eTag和last-modified
//服务器部分
app.get("/axios", async (req, res) => {
console.log(1);
const filePath = path.resolve(__dirname, "axios.min.js");
//获取文件的唯一标识
const Etag = etag(filePath);
//获取文件的最后修改时间 fs.stat可以得到文件的详情
const stat = promisify(fs.stat);
const fileStat = await stat(filePath)
console.log(fileStat.mtime.toGMTString());
const lastModified = fileStat.mtime.toGMTString();
//获取请求对象携带的if-modified-since if-none-match
console.log(req.headers);
const ifNoneMatch = req.headers["if-none-match"];
const ifModifiedSince = req.headers['if-modified-since'];
//把请求携带的信息和服务端文件的信息对比,如果有一个不一致,则重新响应文件
if (ifNoneMatch !== Etag || ifModifiedSince !== lastModified) {
//当读取新资源的时候,需要重新设置文件唯一标识 和最后修改时间的 响应头
res.set("etag", Etag);
res.set("last-modified", lastModified);
return res.sendFile(filePath);
}
//如果上边的判断不成立,则需要读取缓存
res.status(304).send();
})
3.压缩
app.get("/", async (req, res) => {
const filePath = path.resolve(__dirname, "./index.html")
const rs = fs.createReadStream(filePath);
//查看支持的压缩格式
const accpetEncoding = req.headers['accept-encoding'];
console.log(accpetEncoding)
//根据客户端的支持的格式来进行不同的压缩
if (accpetEncoding.includes("gzip")) {
//zlib.createGzip()//创建一个gzip压缩的盒子,能够被流式写入
const gzipFile = rs.pipe(zlib.createGzip()) //返回一个gizp压缩格式的可读流
//告诉客户端我响应的压缩格式是什么
res.set("content-encoding", "gzip")
//把压缩好的文件写入到响应中
return gzipFile.pipe(res) //22.1kb
}
//根据客户端的支持的格式来进行不同的压缩
if (accpetEncoding.includes("deflate")) {
//zlib.createDeflate()//创建一个gzip压缩的盒子,能够被流式写入
const gzipFile = rs.pipe(zlib.createDeflate()) //返回一个gizp压缩格式的可读流
//告诉客户端我响应的压缩格式是什么
res.set("content-encoding", "deflate")
//把压缩好的文件写入到响应中
return gzipFile.pipe(res) //22.1kb
}
//没有压缩的响应
rs.pipe(res) //99.1kb
})
事件轮询机制
1. Global对象
2. nodejs事件轮询机制
3. 浏览器事件轮询机制
回调函数:将函数A作为函数B的参数,并且函数A在函数B内进行调用
3.1 代码分类
- 初始化代码(同步代码):设置定时器、绑定事件,发送ajax等等
- 回调执行代码(异步代码):定时器回调函数,事件回调函数,ajax回调函数
3.2 轮询机制
- 浏览器先执行同步代码,再执行异步代码。
- 在执行同步代码的时候,把异步代码交给浏览器的管理模块进行管理(事件管理模块、ajax管理模块,定时器管理模块)。
- 当异步代码的回调函数需要执行的时候,会把回调函数放在回调队列(任务队列)中等待执行。
- 当同步代码执行完毕之后,主线程会去任务队列中轮询,并将任务队列中的任务(回调函数)取出来执行,主线程不断重复这一步,因此叫做事件轮询。
4. 宏任务和微任务
-
异步代码有优先级关系。有的优先级高先执行,有的优先级低后执行。分为宏任务(macrotask )和微任务(microtask )
-
微任务是js自身发起的: Promise.then/catch/fanally,await语句后的内容,process.nextTick,queueMicrotask
-
宏任务是宿主发起的:包括整体代码script,setTimeout,setInterval等等
-
js引擎执行异步代码。会优先执行微任务,再执行宏任务
-
过程如下:
5.1 执行栈选择最先进入队列的宏任务(一般都是script),执行其同步代码直至结束;
5.2 检查微任务队列中是否存在微任务,有则会执行至微任务队列为空;
5.3 执行宏任务中的异步代码
5.4 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
5.5 当前宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
5.6 当开始下一个宏任务(从事件队列中获取)
模块化
1.原始
2.commonJS模块化
3.ES6模块化规范
webpack
1.基础配置板块
五大’护法’
-
entry:入口起点(entry point)
-
打包时,第一个被访问的源码文件,指示webpack应该使用哪个模块(webpack中一切都是模块),来作为构建其内部依赖图的开始
-
默认是src/index.js(可以通过配置文件指定)
-
webpack可以通过入口,加载整个项目的依赖
-
-
output:出口
- 打包后,输出的文件名称
- 默认是dist/main.js(可以通过配置文件指定)
-
loader :加载器
- loader让webpack能够去处理那些非JavaScript文件(webpack自身只能解析JavaScript)
-
plugins:插件
- 实现loader之外的其他功能(打包优化和压缩等)
- 是Webpack的支撑,用来实现丰富的功能
-
mode :模式
- 生产模式 production
- 开发模式 development
2.使用webpack配置文件
2.1打包详细配置
2.1 .1打包CSS
2.1.1.1 基本打包
- 打包CSS包含:打包逻辑、打包LESS、打包成独立的CSS文件、添加样式前缀、格式校验、压缩CSS
- css-loader:将CSS转换为JS(将css输出到打包后的js文件中)
- style-loader:把包含CSS内容的JS代码,挂载到页面的style标签中
- 需要在入口文件引入CSS(import’./css/main.css’)
- 加载器配置:use:[‘style-loader’,‘css-loader’]
- 安装 npm i css-loader style-loader -D
rules[
{
//匹配后缀名
test:/\.css$/i,
use:[
//use中loader加载是有顺序的,先上后下,注意有的loader需要按照顺序书写
'style-loader',
'css-loader'
]
}
]
2.1.1.2 打包less
-
less-loader:打包less的加载器
-
匹配后缀名
-
加载器配置:use:[‘style-loader’,‘css-loader’,‘less-loader’]
-
安装 npm i less less-loader -D
rules: [{
test: /\.less$/i,
use: [
//use中loader加载是有顺序的,先下后上,注意有的loader需要按照顺序书写
"style-loader",
'css-loader',
'less-loader'
]
}]
React
1.基本使用
1.1 相关js库
- react.js:React核心库。
- react-dom.js:提供操作DOM的react扩展库。
- babel.min.js:解析JSX为JS的库。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<!-- react的核心包 包含了react的核心内容 -->
<script src="./js/react.development.js"></script>
<!-- react中处理虚拟DOM和diffing算法的工具 -->
<script src="./js/react-dom.development.js"></script>
<!-- react中使用的jsx语法,babel用来编译jsx语法 -->
<script src="./js/babel.min.js"></script>
</head>
<body>
<div id="app"></div>
<!-- 因为script标签中写的是jsx语法,所以我们要一如babel并且设置script标签的type为text/babel -->
<script type="text/babel">
// 定义一个虚拟DOM
const vDOM = <h1>hello world</h1>;
// 使用ReactDOM的render方法 把虚拟DOM渲染在真实DOM中
ReactDOM.render(vDOM, document.getElementById("app"));
</script>
</body>
</html>
1.2 js方式创建虚拟DOM
<!-- 此处用js写虚拟DOM所以不用定义type,默认就是text/javascript -->
<script>
// 利用js创建一个虚拟DOM
// const vDOM = React.createElement(
// "div",
// {
// className: "box",
// // data-index:1,
// // dataIndex: 1,
// "data-index": 1,
// },
// "欢迎"
// );
// 嵌套
const vDOM = React.createElement(
"div",
{
className: "box",
// data-index:1,
// dataIndex: 1,
"data-index": 1,
},
React.createElement(
"h1",
null,
React.createElement("span", null, "title")
)
);
// 使用ReactDOM的render方法 把虚拟DOM渲染在真实DOM中
ReactDOM.render(vDOM, document.getElementById("app"));
1.3 jsx方式创建虚拟DOM
<style>
.red {
color: red;
}
</style>
</head>
<body>
<div id="app"></div>
<!-- 因为script标签中写的是jsx语法,所以我们要一如babel并且设置script标签的type为text/babel -->
<script type="text/babel">
const vDOM = (
<div>
<h3>boring</h3>
<ul>
<li className="red">boring1</li>
<li>boring2</li>
<li>boring3</li>
</ul>
</div>
);
// 使用ReactDOM的render方法 把虚拟DOM渲染在真实DOM中
ReactDOM.render(vDOM, document.getElementById("app"));
1.4 虚拟DOM与真实DOM
虚拟DOM:
-
本质就是Object类型的对象(一般对象)
-
虚拟DOM比较“轻”,真实DOM比较“重”,虚拟DOM是react内部在用的,无需真实DOM身上那么多属性
-
虚拟DOM早晚会被react转变成真实DOM,呈现在页面上
<script type="text/babel" >
//1.创建一个虚拟DOM
const VDOM = <h1>Hello,React</h1>
console.log(VDOM) //输出的是虚拟DOM,本质就是Object类型的一般对象
const TDOM = document.getElementById('test')
console.log(TDOM) //输出的是真实DOM
debugger;
//2.将虚拟DOM准备真实DOM渲染到页面
ReactDOM.render(VDOM,document.getElementById('test'))
</script>
1.5 React JSX语法
js表达式:有返回值 js语句:没有返回值
-
定义虚拟DOM时,不要写引号
-
标签结构中要混入js表达式,需要用{}做分割,即:{js表达式}
-
指定标签的类名时,用className
-
行内样式,要用style={{}} ,外面一层{}是表示js区域,里面的{}表示对象,里面填写键和键值,像font-size这种属性,要转为fontSize
-
只能有一个根标签
-
标签必须闭合
-
标签首字母:
-
如果标签首字母小写:则该标签转为html中同名元素,若若html中无该元素,则报错。
-
如果标签首字母大写:则表明是React去渲染的对应组件,若没有定义过该组件,则报错。
-
1.6 JSX中的js区域{}里面值表现
jsx中{}得到不同类型的值的反应
-
string和number:直接插入
-
null,undefined,true,false:直接为空
-
数组:把数组的值按顺序放入虚拟DOM中
-
对象:对象不允许作为jsx插值中的值
<script type="text/babel">
const arr = ["Angular", "React", "Vue"];
const vDOM = (
<div>
<h1>前端框架</h1>
<ul>
{arr.map((item, index) => {
//每一个虚拟DOM变化的地方都有一个key值,以便以后改变的时候有对比参照
return <li key={index}>{item}</li>;
})}
</ul>
</div>
);
ReactDOM.render(vDOM, document.getElementById("box"));
2.React中组件
2.1 函数式组件
如果构造函数实例化后,其返回值:
- 如果构造函数里面没有return,或者return了一个基本数据类型,则函数返回一个实例化对象
- 如果构造函数内部返回了一个复杂数据类型,则函数实例化后,则函数返回这个复杂数据
<script type="text/babel">
//1.声明一个函数式组件
function Dome(){
console.log(this);//undefined
return <h1>我是用函数定义的组件</h1>
}
//2.渲染组件到页面 ReactDOM.render(<Dome/>,getElementById('app'))
</script>
函数式组件定义:
- 定义组件函数时,函数名首字母必须大写
- 该组件函数必须有返回值,返回值一般为虚拟DOM
- 渲染函数组件到页面中时,函数组件必须放到自结束标签中,<函数名 />
函数式组件的特点:
- 里面的this为undefined(在babel严格模式下)
- 没有生命周期
- 没有状态
使用函数组件的过程:
- 寻找组件申明,确认组件类型为函数组件
- 把函数组件给调用(一定不要自己调用函数,而是使用组件调用的方式,否则将不具备任何组件特征)
- 组件调用返回一个虚拟DOM,放到设置的位置中
- 将虚拟DOM转为真实DOM,并渲染到页面中
2.2 类式组件
2.1.1 类的基本知识
公有方法和属性:设置给实例化对象的属性和方法
私有方法和属性:声明在构造函数中的变量或函数
静态方法和属性:js中无需实例化就可以获得的的属性和调用的方法 就是给构造函数自己的属性和方法
//创建一个Person类
class Person{
//构造器方法或函数
constructor(name,age){
//构造器中的this指向类的实例对象
//公有属性
this.name=name;
this.age=age
}
//一般方法
//公有方法
speak(){
//speak方法放在了Person类的原型对象上,供实例使用
//通过Person实例调用speak时,speak中的this就是Person实例
console.log(`我叫${this.name},年龄是${this.age}`)
}
}
//创建一个Person的实例对象
const p1=new Person('tom',18)
const p2=new Person('jerry',20)
//创建一个Student类,继承于Person类
class Student extends Person{
//若和Person类父类属性一致,不需要写构造器,也可以正常继承
constructor(name,age,gender){
//super方法,将与Person类父类属性一致的属性作为参数
super(name,age)
this.gender=gender
}
//重写从父类继承过来的方法
speak(){
console.log(`这是Student的原型对象,我叫${this.name},年龄是${this.age},gender是${this.gender}`)
}
}
const s1=new Student('zs',21,'nan')
s1.speak();
2.1.2 类式组件
class Demo extends React.Component{
render(){
return <h1>我是标题</h1>
}
}
ReactDOM.render(<Demo/>,document.getElementById('app'))
类式组件的定义:
- 使用class声明一个类组件,继承React.Component
- 在类组件中必须要有一个render方法,用来返回一个虚拟DOM
render方法:
- render方法定义在类组件中,属于类组件原型对象上的方法
- 当类组件被使用时,render方法就会被调用
使用类组件的过程:
- 寻找组件申明,确认组件类型为类组件
- 把类组件实例化,得到一个实例化对象(组件实例)
- 组件实例会调用类组件原型对象上的render方法,render方法返回一个虚拟DOM,放到设置的位置中
- 将虚拟DOM转为真实DOM,并渲染到页面中
3. 组件实例三大核心属性一:state
3.1 state基本写法
组件实例重要属性-state
- state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
- state代表的是当前组件的状态,状态中包含了当前组件需要的数据
- 如果虚拟DOM使用了状态中的数据,则修改状态中的数据,虚拟DOM会随之更新
- state设置给实例对象的,state需要定义在类的constructor中 this.state={XXX:XXX}
3.2 修改state的方法
方法:
- state不能直接修改,需要使用组件实例的setState方法
- setState接受的参数是一个对象,将来会合并给原有的state中
练习:
3.3 数量加减的练习
4. React绑定事件
4.1 基础绑定事件
class Demo extends React.Component{
render(){
return <button onClick={this.printWeather}>点击<button/>
}
//事件函数,位于Demo原型对象上
printWeather(){
console.log('今天天气好')
console.log('this',this)//this为undefined
}
}
ReactDOM.render(</Demo>,document.getElementById('app'))
事件定义:
- 命名采用小驼峰方法命名,放在绑定的标签中
- 被绑定的函数位于jsx的js{}区域
- 一般函数为原型上或实例上方法,需要用this.方法名调用,this不能漏了
这个基础绑定事件里面,有一个问题就是:React里面默认事件调用函数里面的this为undefined
4.2 绑定事件进阶
4.2.1 方法一
class Demo extends React.Component{
render(){
this.printWeather=this.printWeather.bind(this)
return <button onClick={this.printWeather}>点击<button/>
}
//事件函数,位于Demo原型对象上
printWeather(){
console.log('今天天气好')
console.log('this',this)//this为调用的实例对象
}
}
ReactDOM.render(</Demo>,document.getElementById('app'))
4.2.1 方法二
class Demo extends React.Component{
constructor(){
super()//不可少,少了无法得到this
this.printWeather=this.printWeather.bind(this)
}
render(){
return <button onClick={this.printWeather}>点击<button/>
}
//事件函数,位于Demo原型对象上
printWeather(){
console.log('今天天气好')
console.log('this',this)//this为调用的实例对象
}
}
ReactDOM.render(</Demo>,document.getElementById('app'))
4.3 绑定事件和state简写
constructor函数的两个用法:
- 需要实例化的时候传参
- this指向组件实例
但是我们在未来react中基本不用实例化传参,直接在class语法中给一个变量赋值,这个变量就属于组件实例的属性
class Demo extends React.Component{
//直接写在类class里面的属性,是实例的属性
state={
project:'js',
}
//事件函数的简写位置
printWeather=this.printWeather.bind(this)
render(){
return <button onClick={this.printWeather}>点击<button/>
}
//事件函数,位于Demo原型对象上
printWeather(){
console.log('今天天气好')
console.log('this',this)//this为调用的实例对象
}
}
ReactDOM.render(</Demo>,document.getElementById('app'))
4.4 真正绑定事件写法
4.4.1 方法一
4.4.2 方法二
4.5 React事件的event事件对象
js事件:
- onput事件:当表单内容发生改变就会触发
- onchange事件:当表单内容发生改变并失去焦点的时候触发
- React的onChange事件是当前表单内容发生改变就会触发
React事件对象:
- 可以直接获取事件对象
- 不能使用return false来阻止默认事件,需要直接使用preventDefault()来阻止默认事件
- e.target可以获取当前的元素,就可以不用使用ref
class App extends React.Component {
render() {
return (
<div>
<a href="http://www.baidu.com" onClick={(e)=>{e.preventDefault()}}></a>
<button onClick={e=>{console.log(e.target.textContent)}}>点击可以获取内容</button>
<input type="text" onChange={e=>{console.log(e.target.value)}} />
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
5. 组件实例三大核心属性二:props
5.1 基础的传值方式
props:
- 主要用于外部向组件内部传值使用
- 写法就是在组件标签上书写属性
- 接受props的组件,可以在组件实例上访问到props对象
- 不要修改props的值,props是只读的
class Myself extends React.Component{
//组件实例内部,可以通过this.props拿到从外部传过来的值,不要修改props的值,props是只读的
render(){
console.log(this.props)//看下图1
let {name,age,gender}=this.props.mes;
//如果真的要修改值,则可以结构出来变量,然后对变量进行修改
name+='~';
return (
<ul>
<li>我的名字是{name}</li>
<li>我的性别是{age}</li>
<li>我的年龄是{gender}</li>
</ul>
)
}
}
class App extends React.Component{
state={
persons:{
[name:'zs',age:20,gender:'n'],
[name:'ls',age:21,gender:'w'],
[name:'ws',age:23,gender:'w'],
}
}
render(){
const {persons}=this.state;
return (
<div>
<Myself mes={persons[0]}/>
<Myself mes={persons[1]}/>
<Myself mes={persons[2]}/>
</div>
)
}
}
ReactDOM.render=(<App/>,document.getElementById('app'))
this.props 对象:(图一)
5.2 props传值
class Myself extends React.Component {
render() {
console.log(this);
console.log(this.props);
const { name, age, gender } = this.props.mes;
return (
<ul>
<li>姓名是:{name}</li>
<li>年龄是:{age}</li>
<li>性别是:{gender}</li>
</ul>
);
}
}
class App extends React.Component {
state = {
persons: [
{ name: "zs", age: 20, gender: "nan" },
{ name: "ls", age: 18, gender: "wn" },
{ name: "wl", age: 25, gender: "nan" },
],
};
render() {
const { persons } = this.state;
return (
<div>
{persons.map((item, index) => {
//每一个虚拟DOM变化的地方都有一个key值,以便以后改变的时候有对比参照
return <Myself mes={item} key={index} />;
})}
</div>
);
}
}
5.3 props批量传值
{…obj} 在jsx中书写时,可以展开对象为key-value(jsx+react实现的,不是js语法)
class Myself extends React.Component {
render() {
console.log(this);
console.log(this.props);
const { name, age, gender } = this.props;
return (
<ul>
<li>姓名是:{name}</li>
<li>年龄是:{age}</li>
<li>性别是:{gender}</li>
</ul>
);
}
}
class App extends React.Component {
state = {
persons: [
{ name: "zs", age: 20, gender: "nan" },
{ name: "ls", age: 18, gender: "wn" },
{ name: "wl", age: 25, gender: "nan" },
],
};
render() {
const { persons } = this.state;
return (
<div>
{persons.map((item, index) => {
// {...obj}在jsx中书写时,可以展开对象为key-value(jsx+react实现,不是js语法)
// 后面再给组件添加属性,也没有影响,this.props会直接展开为一个对象
return <Myself {...item} index={index} key={index} />;
})}
</div>
);
}
}
5.4 配置props限制
使用:
- 引入第三方包
- 设置为要传入值的构造函数的静态属性 class类组件里面可以static配置或外面构造函数的静态属性
class Myself extends React.Component {
static propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.string,
gender: PropTypes.oneOf(["nan", "wn"]),
};
render() {
console.log(this);
const { name, age, gender } = this.props;
return (
<ul>
<li>姓名是:{name}</li>
<li>年龄是:{age}</li>
<li>性别是:{gender}</li>
</ul>
);
}
}
// Myself.propTypes={
// name:PropTypes.string.isRequired,
// age:PropTypes.number,
// gender:PropTypes.string,
// gender:PropTypes.oneOf(['nan','wn'])
// }
class App extends React.Component {
state = {
persons: [
{ name: "zs", age: 20, gender: "nan" },
{ name: "ls", age: 18, gender: "wn" },
{ name: "wl", age: 25, gender: "nan" },
],
};
render() {
const { persons } = this.state;
return (
<div>
{persons.map((item, index) => {
return <Myself {...item} key={index} />;
})}
</div>
);
}
}
5.5 函数式组件的props写法
过程:
- 判断组件类型,确认为函数式组件
- 判断为函数式组件后,则调用函数
- 将组件中的属性合并到一个对象传给函数作为参数
function Myself({ name, age, gender }) {
return (
<ul>
<li>姓名是:{name}</li>
<li>年龄是:{age}</li>
<li>性别是:{gender}</li>
</ul>
);
};
Myself.propTypes = {
name: PropTypes.string.isRequired,
age: PropTypes.number,
gender: PropTypes.string,
};
const persons = [
{ name: "zs", age: 20, gender: "nan" },
{ name: "ls", age: 18, gender: "wn" },
{ name: "wl", age: 25, gender: "nan" },
];
function App() {
return (
<div>
{persons.map((item, index) => {
let { name, age, gender } = item;
return (
<Myself name={name} age={age} gender={gender} key={index} />
);
})}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("app"));
6. 组件实例三大核心属性三:refs
使用情景:
- 管理焦点,文本选择或媒体播放
- 触发强制动画
- 集成第三方DOM库
6.1 refs的字符串方式-方法一:
基本步骤:字符串方式
- refs主要是React用来操作DOM的
- ref的字符串方法,只需要在备货的DOM上设置ref属性,值为一个自定义名称XXX 类似于设置ID给标签
- 在当前组件的其他位置可以通过this.refs.XXX(设置的名称)获取当前DOM
class App extends React.Component {
render() {
console.log(this);
return (
<div>
<input type="text" ref="oIpt" />
<button onClick={this.printMsg}>打印输出内容</button>
</div>
);
}
printMsg = () => {
console.log(this.refs.oIpt.value);
console.log(this.refs);
};
}
ReactDOM.render(<App />, document.getElementById("app"));
6.2 refs的回调函数方式-方法二
基本步骤:回调形式
- 给被获取的DOM设置ref属性
- ref属性的值是一个函数(箭头函数)
- 当读取到ref属性时,React会将ref属性的回调函数调用,并传递当前的DOM为实参
- 把这个接受实参的形参赋值给实例对象的一个属性,这个属性就是被获取的DOM
class App extends React.Component {
render() {
console.log(this);
return (
<div>
<input type="text" ref={(c) => (this.oIpt = c)} />
<button onClick={this.printMsg}>打印输出内容</button>
</div>
);
}
printMsg = () => {
console.log(this.oIpt);
console.log(this.oIpt.value);
console.log(this.refs);
};
}
ReactDOM.render(<App />,document.getElementById("app"));
6.3 refs的创建容器方式-方法三
基本步骤:创建容器方式
- 首先使用React.createRef()方法创建一个容器
- 把这个容器给到要被获取的DOM节点的ref属性上
- 通过this.容器.current获取到当前的DOM
class App extends React.Component {
//设置一个ref的容器
oIpt = React.createRef();
render() {
console.log(this);
return (
<div>
<input type="text" ref={this.oIpt} />
<button onClick={this.printMsg}>打印输出内容</button>
</div>
);
}
printMsg = () => {
console.log(this.oIpt);
console.log(this.oIpt.current.value);
console.log(this.refs);
};
}
ReactDOM.render(<App />, document.getElementById("app"));
7.React收集表单数据
7.1 非受控表单
class Demo extends React.Component {
render() {
return (
<form action="###">
用户名:
<input type="text" ref={(c) => (this.user = c)} />
<br />
密码:
<input type="text" ref={(c) => (this.pass= c)} />
<br />
<button onClick={this.login}>登录</button>
</form>
)
}
login = (e) => {
e.preventDefault();
console.log(`用户名是${this.user.value},密码是${this.pass.value}`);
};
}
7.2 受控表单
class Demo extends React.Component {
state = {
user: "",
pass: "",
};
render() {
return (
<form action="###">
用户名:
<input type="text" onChange={this.setUser} />
<br />
密码:
<input type="text" onChnage={this.setPass} />
<br />
<button onClick={this.login}>登录</button>
</form>
);
}
setUser = (e) => {
this.setState({ user: e.target.value });
};
setPass = (e) => {
this.setState({ user: e.target.value });
};
login = (e) => {
e.preventDefault();
const { user, pass } = this.state;
console.log(`用户名是${user},密码是${pass}`);
};
}
7.3 受控表单高阶写法
class Demo extends React.Component {
state = {
user: "",
pass: "",
};
render() {
return (
<form action="###">
用户名:
<input type="text" onChange={this.setFormData("user")} />
<br />
密码:
<input type="text" onChange={this.setFormData("pass")} />
<br />
<button onClick={this.login}>登录</button>
</form>
);
}
setFormData = (type) => {
return (e) => {
this.setState({ [type]: e.target.value });
};
};
login = (e) => {
e.preventDefault();
const { user, pass } = this.state;
console.log(`用户名是${user},密码是${pass}`);
};
}
7.4 高阶函数和函数柯里化
高阶函数:如果有函数A,A只需满足以下2个条件中任意一个,A就是高阶函数
- 若A函数接受的参数是一个参数,例如数组相关方法,promise,setTimeout
- 若A函数调用后,返回值任然是一个函数,例如防抖节流函数
函数柯里化:通过函数调用,继续返回函数的方式,实现多次接受参数的函数形式
// 函数柯里化
function add(a) {
return function (b) {
return a + b;
};
}
add(1)(2);
8. React组件生命周期
8.1 生命周期引入
<div id='app'></div>
<script type='text/babel'>
class App extends React.Component{
state = {
opacity : 1
}
componentDidMount(){
let {opacity} = this.state
console.log(opacity);
this.timer = setInterval(()=>{
opacity -= 0.1;
if(opacity<=0){
opacity = 1
}
this.setState({opacity})
},200)
}
render(){
const {opacity} = this.state;
return (
<div>
<p style={{opacity:opacity}}>React 太简单了,都是对勾</p>
<button onClick={()=>{ReactDOM.unmountComponentAtNode(document.getElementById('app'))}}>从入门到放弃</button>
</div>
)
}
componentWillUnmount(){
clearInterval(this.timer)
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
8.2 生命周期挂载流程
初始化阶段由ReactDOM.render()触发—初次渲染
若触发流程5,组件卸载不再呈现在页面中
- React组件挂载流程1-构造器constructor()
- React组件挂载流程2-React组件即将挂载 componentWillMount()
- React组件挂载流程3-渲染render()
- React组件挂载流程4-React组件已经挂载完成componentDidMount()
- React组件挂载流程5-React组件即将卸载componentWillUnmount() ,由ReactDOM.unmountComponentAtNode()触发,参数为组件卸载的父节点
class App extends React.Component {
// 挂载流程1 :constructor构造器
constructor() {
super();
console.log("constructor 执行了");
}
// 挂载流程2 :React组件即将挂载
componentWillMount() {
console.log("componentWillMount执行了");
}
// 挂载流程3:React组件渲染
render() {
console.log("render 执行了");
return (
<div>
<p>更新阶段 由组件内部this.setSate()或父组件重新render触发</p>
<button
onClick={() => {
ReactDOM.unmountComponentAtNode(
document.getElementById("app")
);
}}
>
从入门到放弃
</button>
</div>
);
}
// 挂载流程4:React组件已经完成挂载
componentDidMount() {
console.log(" componentDidMount执行了");
}
// 挂载流程5:React组件即将卸载 由ReactDOM.unmountComponentAtNode()触发,参数为组件从哪里卸载
componentWillUnmount() {
console.log(" componentWillUnmount执行了");
}
}
ReactDOM.render(<App />, document.getElementById("app"));
8.3 生命周期更新流程
由组件内部this.setSate()或父组件重新render触发
-
React组件更新流程1-阀门,可以控制state更新后,render函数是够渲染 shouldComponentUpdate() 必须返回布尔值 return true (渲染)/false(不渲染);此处若是false,后面的流程2-4不会触发执行
-
React组件更新流程2-组件将要更新 componentWillUpdate() , this.forceUpdate()可以触发强制更新
如果流程触发了强制更新,前面流程1-shouldComponentUpdate() 不管返回什么,都不会生效执行
-
React组件更新流程3-渲染render()
-
React组件更新流程4-组件完成更新 componentDidUpdate()
-
React组件更新流程5-React组件即将卸载componentWillUnmount(),由ReactDOM.unmountComponentAtNode()触发,参数为组件卸载的父节点
class App extends React.Component {
// 挂载流程1 :constructor构造器
constructor() {
super();
console.log("constructor 执行了");
}
state = {
count: 0,
};
// 挂载流程2 :React组件即将挂载
componentWillMount() {
console.log("componentWillMount执行了");
}
// 挂载流程3:React组件渲染
render() {
console.log("render 执行了");
let { count } = this.state;
return (
<div>
<h1>{count}</h1>
<p>更新阶段 由组件内部this.setSate()或父组件重新render触发</p>
<button onClick={() => {this.setState({count:++count})}}>+</button>
<br />
<button onClick={() => {this.forceUpdate()}}>强制更新</button>
<button
onClick={() => {
ReactDOM.unmountComponentAtNode(
document.getElementById("app")
);
}}
>
从入门到放弃
</button>
</div>
);
}
// 挂载流程4:React组件已经完成挂载
componentDidMount() {
console.log(" componentDidMount执行了");
}
//更新流程2:阀门,可以控制state更新后,render函数是否渲染
shouldComponentUpdate() {
console.log("---componentShouldUpdate执行了---");
return true;
}
// 更新流程3:即将更新 this.forceUpdate()方法可以要求强制更新
componentWillUpdate() {
console.log("---componentWillUpdate执行了---");
}
// 更新流程4:更新已完成
componentDidUpdate(){
console.log("---componentDidUpdate执行了---");
}
// 挂载流程5:React组件即将卸载 由ReactDOM.unmountComponentAtNode()触发,参数为组件从哪里卸载
componentWillUnmount() {
console.log(" componentWillUnmount执行了");
}
}
ReactDOM.render(<App />, document.getElementById("app"));
8.4 生命周期父传子更新流程
子中更新流程新增:componentWillReceiveProps()
- 初次渲染过程,父与子正常渲染,子渲染流程是不会执行上面的生命函数的;
- 后面父组件只要重新渲染,无论是否接受父组件的props都要执行这个生命周期函数,而且所有更新流程也要执行
class App extends React.Component {
state = {
weather: "hot",
count: 0,
};
render() {
let { weather ,count } = this.state;
return (
<div>
<p>{weather},{count}</p>
<button onClick={()=>{this.setState({weather:'cool'})}}>改变天气</button>
<br/>
<button onClick={()=>{this.setState({count:++count})}}>改变count</button>
<p>我是父组件 我下边是子组件</p>
<Son />
</div>
);
}
}
class Son extends React.Component{
render (){
return (
<div>
<button onClick={()=>{ReactDOM.unmountComponentAtNode(document.getElementById('app'))}}>卸载组件</button>
</div>
)
}
componentWillMount(){
console.log("即将渲染了");
}
componentDidMount(){
console.log("已经渲染了");
}
//此步骤:子组件渲染流程的时候是不执行的
// 父组件只要重新渲染,无论是否接受父组件的props都要执行这个生命周期函数
componentWillReceiveProps(){
console.log('即将接受props');
}
shouldComponentUpdate(){
console.log("---shouldComponentUpdate---");
return false;
}
componentWillUpdate(){
console.log("---componentWillUpdate---");
}
componentDidUpdate(){
console.log('---componentDidUpdate---');
}
componentWillUnmount(){
console.log('---componentWillUnmount---');
}
}
ReactDOM.render(<App />, document.getElementById("app"));
9.虚拟DOM和DOM Diffing算法
Diffing算法
/*
//旧
<li key=0>laowang input</li>
<li key=1>laozhang input</li>
<li key=2>laojun input</li>
//新
<li key=4>laoyang input</li>
<li key=0>laowang input</li>
<li key=1>laozhang input</li>
<li key=2>laojun input</li>
*/
class App extends React.Component{
state = {
person:[
{id:"001",name:"laowang"},
{id:"002",name:"laozhang"},
{id:"003",name:"laojun"}
]
}
render(){
const {person} = this.state
return (
<div>
<button onClick={this.addPerson}>添加</button>
<ul>
{
person.map((item,index)=>{
return <li key={item.id}>{item.name} <input type="text"/></li>
})
}
</ul>
</div>
)
}
addPerson = () => {
const {person} = this.state;
person.unshift({id:"004",name:"laoyang"})
this.setState({person})
}
}
ReactDOM.render(<App/>,document.getElementById('app'))
10.脚手架
10.1 useState
步骤:
- const [要存的变量名,修改变量的方法名]=useState(变量内容)
- 用useContext暴露达到传递的目的
10.2 useContext
步骤:
- 父组件 createContext
- 利用createContext()创造对应实例组件对象,同时将其暴露出去
- 将要传数据的子组件放在<实例组件对象名.Provider><实例组件对象名.Provider/>组件标签中
- 该组件标签上有一属性value,可以填写要传给子组件的数据 value={{}}
- 子组件里引入useContext ,用useContext(实例组件对象名),接收数据
10.3 useEffect
用法:
- useEffect通用副作用函数,可以取代生命周期中的componentDidMount、componentDidUpdate、componentWillUnmount
- useEffect接受的第一个参数是函数,如果没有第二个参数的情况下,无论是更新还是初始挂载都会执行这个函数
- useEffect接受第二个参数是数组,可以限定数组中哪一个状态变化的时候,再执行useEffect的函数,如果是空数组,则无论状态怎么更新,都不会再次执行
- 可以设置多个useEffect,也建议不同的功能有不同的useEffect
- useEffect返回一个函数,当这个组件被卸载之前,会调用这个函数
10.4 React-Router-DOM旧版本
10.4.1 基础配置
- 在 App 组件外层嵌套一个 BrowserRouter 组件
- 所有导航使用 Link 组件 内部是 to 属性定义路由地址
- 路由展示区域使用 Route 组件,path 用来对应路径 component 属性用来确定组件
10.4.2 NavLink
- 当导航组件点击需要切换样式的时候,可以给导航使用 NavLink 组件
- NavLink 组件提供 activeClassName 属性,里边放的是获取焦点后应该加载的类
10.4.3 自定义 Link 组件
- 自定一个 Link 组件,通过 props 传值进即可
- 组件的标签内的内容 也是可以在 props.children 中得到的
10.4.4 Switch 组件
- 可以把多个 Route 放在一个 Switch 组件中,此时只要匹配到一个 Route,则退出整个 Switch
- 可以拥有多个 Switch
10.4.5 严格匹配
- 在 Route 组件上有一个 exact 属性,值是布尔值,负责开启严格匹配
- 严格匹配:(to:/home/news) (path:/home) 此时是无法匹配的
- 我们一般不开启严格匹配,否则二级路由无法访问
10.4.6 Redirect
- 重定向
- 当所有的 Route 没有完成匹配的时候,则自动走 Redirect 组件,Redirect 组件把地址重定向到我们预设的一个默认地址
10.4.7 组件划分
- 路由组件(pages)和一般组件(components)
- 一般组件的 props 就是我们传的值
- 路由组件的 props 是由 history location match 等对象组成的一个对象,提供了很多操作路由的方法
10.4.8 二级和三级路由
- 正常向一级一样书写即可
- 路由切换的时候 默认是组件创建和销毁
10.4.9 params 路由传参
- 路由导航书写接受参数的变量 /user/:id/:say
- Link 中的路径中拼接需要数据 /user/001/hello
- 在路由组件中 使用 props.match.params 得到想要的数据
10.4.10 search 路由传参
- 路由导航正常书写路径
- 在 Link 的路径中拼接数据为查询字符串
- 在路由组件中 使用 props.location.search 得到想要的查询字符串
- 通过 qs 工具 转为对象
10.4.11 state 路由传参
- 路由导航的 to 属性写成对象 {pathname:路由路径 state:数据}
- 在路由组件中 通过 props.location.state 得到想要的数据
10.4.12 编程式路由导航
- Link 默认转为 a 标签,很多时候不用 a 标签,或者不用点击等情况下需要路由跳转
- props.history.push(path,state)进行编程式路由导航
10.4.13 replace
- 声明式路由导航中 使用Link组件的 replace属性为boolean值 来进行确定是否替换历史记录
- 编程式路由导航中 使用props.history.push/replace方 法来确定是否留下历史记录
10.4.14 前进后退历史纪录
- 使用props.history.go/goBack/goForward
10.4.15 withRouter
- 可以让一般组件拥有路由组件的props对象
- 暴露组件的时候 直接把组件名放在 withRouter方法中暴露出去
10.5 React-Router-DOM v6 版本
10.5.1 BrowserRouter
BrowserRouter
组件最好放在最顶层所有组件之外,这样能确保内部组件使用 Link 做路由跳转时不出错(相比较 v5 没有变化)
10.5.2 Switch 重命名为 Routes
10.5.3 基础写法
- Link 组件用来定义选项
- Route 组件用来定义响应(route 内部加载的组件使用 element 属性 值为对应的组件标签)
NavLink组件
的style
或className
可以接收一个函数,函数接收一个isActive参数
,可根据该参数调整样式
<NavLink
to="/about"
className={({ isActive }) => {
return isActive ? "list-group-item active" : "list-group-item";
}}
>
About
</NavLink>
<NavLink
to="/home"
className={({ isActive }) => {
return isActive ? "list-group-item active" : "list-group-item";
}}
>
Home
</NavLink>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
10.5.4 路由传参
- params 传参
- 传值方式不变
- 接收值的方式
import { useParams } from "react-router-dom";
console.log(useParams());
- search 传参
- 传值方式不变
- 接收值的方式
import { useLocation } from "react-router-dom";
console.log(useLocation());
- state 传参
- 使用编程式路由导航
import { useNavigate } from "react-router-dom";
<button
onClick={() => {
to("/list", { state: { say: "byebye" } });
}}
></button>;
接收值的方式
import { useLocation } from "react-router-dom";
console.log(useLocation());
- 二级路由
- 在一级路由的 Route 中书写 path 为:
<Route path="/about/*" element={<About />} />
- 在二级路由的时候
- 在一级路由的 Route 中书写 path 为:
<h3>我是About的内容</h3>
<Link to="/about/hello">hello</Link>
<Link to="/about/world">world</Link>
<Routes>
<Route path="hello" element={<Hello />}></Route>
<Route path="world" element={<World />}></Route>
</Routes>
正则:
正则对象调用test方法,传入字符串,返回布尔值 (看符不符合正则要求)
字符串:
字符串调用match方法,传入正则规则,返回符合要求的字符串组成的数组
调用replace方法,替换关键字
正则表达式方法
字符串方法
数组方法
对象方法