React全家桶(技术栈)
一、React介绍
1.1- 什么是React
官方:用于构建用户界面的 JavaScript 库 。
所以React并不是一个框架,而是一个用于前端数据渲染的库。
而人们常指的React框架指的是:
react+react-router+react-redux的组合。React框架是渐进式的。
React 起源于 Facebook 的内部项目,最初来架设 Instagram ,后于 2013 年 5 月开源。
1.2- React的特点
高性能:虚拟DOM(Virtual DOM)配合 diff 算法,最大限度地减少与 DOM 的交互。
声明式:React采用声明式编码UI,可以轻松实现交互式UI,让代码更加可行且方便调试。
组件化:通过 React 构建组件,使得代码更加容易得到复用,能够高效率的应用在大项目的开发中。
单向响应的数据流:React 中的数据是单向自顶向下传递的,父组件数据的更新会自动传递到子组件,但子组件的数据更新不会影响到父组件,也不能在子组件修改父组件传入的数据
JSX扩展:JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但官网建议使用它。
灵活:React可以与已知的库或框架很好的进行配合。
1.3- 国内reactjs开发的项目
京东会员中心:home.jd.com/
阿里云: www.aliyun.com/
七牛云控制中心 portal.qiniu.com/create
支付宝商家中心:mrchportalweb.alipay.com/user/home
……
1.4- React官网
英文官网: https://reactjs.org/
中文官网: zh-hans.reactjs.org/
二、React基本操作
2.1- 页面中使用react
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- react核心文件 -->
<script src="./lib/react.development.js"></script>
<!-- 用于DOM操作,构建界面,react-dom依赖于react,所以一定要先引入react再引入react-dom -->
<script src="./lib/react-dom.development.js"></script>
</head>
<body>
<!-- react项目提根元素,该元素提供了react展现的舞台-->
<div id="root">
</div>
</body>
<script>
// 通过ReactDOM.createRoot可以创建React容器
// ReactDOM由react-dom.development.js来提供
// 将id为root的元素作为React容器
const root = ReactDOM.createRoot(document.querySelector("#root"));
// render指定呈现的内容。(指定挂载的数据)
root.render("你现在过的还好吗?");
</script>
</html>
2.2- 注意事项
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<div class="app"></div>
</body>
<script>
{
// 1- 挂载的位置(react容器)同一个页面可以指定多个
// 将id为root的元素作为挂载的容器(react容器)
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render("我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪");
// 将class为app的元素作为挂载的容器(react容器)
// const app = ReactDOM.createRoot(document.querySelector(".app"));
// app.render("我确定你就是那只披着羊皮的狼");
}
{
// 2- render可以调用多次,最后一次呈现的内容,会将之前的覆盖掉
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render("我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪");
// root.render("365只烛光,映在我脸上");
}
{
// 3- 挂载的位置,不要被指定多次
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render("我现在很好!");
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root2.render("你在或不在,我都不等你,你来或不来,也都无所谓");
}
{
// 4- 不要将body或html作为react容器
// const root = ReactDOM.createRoot(document.body);
// root.render("不要将body作为容器,因为body当中的子项,经常被第三方的脚本或浏览器插件使用");
// const root = ReactDOM.createRoot(document.documentElement);
// root.render("不要将html作为容器");
}
{
// 5- render呈现的内容,如果是字符串,字符串使用html标签,不会被浏览器识别
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render("<a>我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪</a>");
}
{
// 6- 可以使用连缀写法
ReactDOM.createRoot(document.querySelector("#root")).render("<a>我爱你中国,我亲爱的母亲,我为你流泪,也为你自豪</a>");
}
</script>
</html>
2.3- 更新与卸载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
</head>
<body>
<button>点我</button>
<button>卸载</button>
<div id="root"></div>
</body>
<script>
// 1- 生成react容器
const root = ReactDOM.createRoot(document.querySelector("#root"));
const btns = document.querySelectorAll("button");
let num = 100;
// 2- 为react容器挂载内容
root.render(num);
btns[0].onclick = function(){
root.render(++num);
}
btns[1].onclick = function(){
// 3- 卸载react容器
root.unmount();
}
</script>
</html>
三- 虚拟DOM
3.1- 虚拟DOM的理解
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
<script>
/*
1- 浏览器渲染的流程是什么?
答:当浏览器发送一个请求,会得到对应的响应。浏览器会通过HTML解析器去解析HTML会构建DOM树,会通过CSS解析器去解析CSS生成CSS规则树。
如果页面中拥有一些JS逻辑,那么往往会通过JS将CSS,HTML进行修改的操作。往往造成重排重绘。
2- 什么是重排重绘?
答:当元素的几何属性(大小,位置相关属性)发生变化后,浏览器会将几何属性进行重新的计算,会影响到其它的元素。
那么受影响的元素对应的几何属性也会发生变化(几何属性会重新计算)。
几何属性进行的计算称为重排。
重排之后,需要将结果进行渲染,渲染的过程称为重绘。
结论:重排之后肯定会进行重绘,重绘不会进行重排。
this.style.width = "100px";
this.style.height="200px";
3- 传统DOM有什么缺陷?
答:当页面比较复杂时,JS如果频繁更改几何属性,那么会产生很多次重排重绘,从而性能会降低,浪费执行时间。
4- React高效的原因是什么?
答:React不会直接操作真实DOM,降低重排重绘。React操作的是虚拟DOM.
5- 虚拟DOM是什么?有什么特点?
答: 1- 虚拟DOM是JS的一个对象,该对象下的属性对应着真实DOM的属性。
2- 虚拟DOM对象当中的属性,是根据实际操作决定的,虚拟DOM是轻量级的,而真实DOM是重量级。
3- 最终虚拟DOM会转为真实的DOM进行渲染。你可以将虚拟DOM理解为真实DOM的一个副本或缓存。
4- 虚拟DOM也被称为虚拟DOM元素,在React当中称为React元素。
5- 如何在React当中创建并使用虚拟DOM
5-1 React.createElement()(了解)
5-2 jsx (重点),jsx的本质其实是React.createElement()的语法糖。
* */
</script>
</html>
3.2- createElement创建虚拟DOM(了解)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<style>
.two{
color:red;
}
</style>
</head>
<body>
<div id="root">
</div>
</body>
<script>
{
// 创建真实DOM
// document.createElement()
// 创建虚拟DOM(英文:virtual dom)
// React.createElement()
}
// 生成React容器
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// 1-字符串当中的HTML标签不会被浏览器识别
root.render("<a>字符串</a>")
}
{
// 2- 可以通过React.createElement创建虚拟DOM,然后通过root.render接收即可
// createElement一般使用三个参数
// 1- 标签的名字(type) 必须填写
// 2- 指定属性 (props) 可以省略
// 3- 指定包裹的内容。(props.children) 可以省略
// 以下虚拟DOM对应着 <div id="one" class="two">最近有没有什么好看电影啊</div>
// const div = React.createElement("div",{
// id:"one",
// className:"two"
// },"最近有没有什么好看电影啊");
// console.log(div);
// root.render(div);// 如果render接收的是一个属性DOM,会将虚拟DOM转为真实DOM进行渲染。
}
{
// 3-第二个参数不可以直接跳过,如果没有属性可以设置,那么可以将第二个参数设置为空对象或null
// const span = React.createElement("span",{},"span标签");
// const span = React.createElement("span",null,"span标签");
// root.render(span);
}
{
// 4- 如果结构比较复杂,使用React.createElement不太适宜。
// 4-1 如果指定的内容是字符串,字符串中又使用了标签,那么虚拟DOM转为真实DOM,不会将span作为浏览器识别标签。
// const div = React.createElement("div",{className:"two"},"<span>span标签</span>");
// root.render(div);
// 4-2 可以先创建span,再创建div,将span(虚拟DOM)作为div的第三个参数
const span = React.createElement("span",null,"span标签");
const p = React.createElement("p",null,"p标签");
// 从第三个参数开始为包裹的元素
// const div = React.createElement("div",{className:"two"},span,span,span,span,span,p);
// 也可以将包裹的元素放置到数组中
const div = React.createElement("div",{className:"two"},[span,span,span,span,span,p]);
root.render(div);
}
{
// 总结:React.createElement可以创建虚拟DOM,可以将创建好的虚拟DOM作为参数传递给render,render会将虚拟DOM转为真实DOM
// 但是React.createElement不太适合复杂的界面设计。
}
</script>
</html>
四- JSX(重点)
它被称为 JSX,是一个 JavaScript 的语法扩展。我们建议在 React 中配合使用 JSX,JSX 可以很好地描述 UI 应该呈现出它应有交互的本质形式。JSX 可能会使人联想到模板语言,但它具有 JavaScript 的全部功能。
JSX 可以生成 React “元素”。
4-1 JSX的基本用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<!-- 可以支持JSX语法 -->
<script src="./lib/babel.min.js"></script>
<style>
.two{
color:red;
}
</style>
</head>
<body>
<div id="root"></div>
</body>
<!--<script src="./lib/my.js" type="text/babel"></script>-->
<script src="./lib/my.jsx" type="text/babel"></script>
<script type="text/babel">
// 创建React容器
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// jsx全称:javascript xml,
// jsx是JAVASCRIPT的一个扩展语法,扩展语法浏览器不支持,借助于babel.js
// 1- 引入babel.min.js
// 2- 在script标签中增加type=text/babel
// <div></div> 相当于React.createElement("div");
// root.render(<div></div>);
}
{
// div中嵌套span,span内写入内容
// 相当于React.createElement("div",{id:"one",className:"two"},"今天学习JSX")
// root.render(<div id="one" className="two">今天学习JSX</div>)
// root.render(<div><span>span1</span><span>span2</span><p>1</p></div>)
// root.render(
// <div>
// <span>span1</span>
// <span>span2</span>
// <p>1</p>
// </div>
// )
}
{
// 强烈建议:如果写入的JSX有嵌套,建议使用()将其包裹
// root.render((
// <div>
// <span>1</span>
// <span>2</span>
// <span>3</span>
// <span>4</span>
// <p>2</p>
// </div>
// ))
}
{
// 元素变量:值是虚拟DOM元素(React元素)的变量
// 元素常量:值是虚拟DOM元素(React元素)的常量
// const vDom = (
// <div>
// <span>1</span>
// <span>2</span>
// <span>3</span>
// <span>4</span>
// </div>
// )
// root.render(vDom);
}
{
// 将元素常量放置到外部定义。 lib->my.js
// 需要script引入,type=text/babel
// root.render(vDom);
}
{
// 如果将元素常量放置到外部,强烈建议:将文件的名字以jsx结尾。
// 在开发过程中,如果你的文件中使用了JSX,一般以.jsx作为扩展名
// root.render(vDom);
}
{
// 如果只是单纯的使用一个标签,建议:不需要使用括号,直接闭合即可
// root.render(<div/>)
}
{
// 注意1:在使用JSX时,标签必须要闭合
// root.render((
// <div>
// <input type="text"/>
// <span>1</span>
// </div>
// ))
}
{
// 注意2:在使用JSX时,标签的名字首字母不允许大写
// 首字母大写会认为你使用的是组件,组件是需要定义的。
// root.render((
// <div>
// <Span></Span>
// </div>
// ))
}
{
// 注意3:在使用JSX时,不允许出现HTML没有内置标签(浏览器不支持的标签,不允许出现)
// root.render((
// <div>
// <user></user>
// </div>
// ))
}
{
// 注意4:传入的JSX有且只能是一个根元素
// root.render((
// <div></div>
// <div></div>
// ))
}
{
// 注意5:如果有显示多个兄弟元素,可以在外围增加一个div
// root.render((
// <div>
// <div>1</div>
// <div>2</div>
// </div>
// ))
}
{
// 注意6:可以使用React提供的包裹组件,来解决一个根元素的问题
// React.Fragment只是起到了一个包裹的作用,不会转为真实dom
// 1
// root.render((
// <React.Fragment>
// <div>1</div>
// <div>2</div>
// </React.Fragment>
// ))
// 2
// const {Fragment} = React;
// root.render((
// <Fragment>
// <div>1</div>
// <div>2</div>
// </Fragment>
// ))
// 3- <></>也是一个包裹标签,是React.Fragment的语法糖。
root.render((
<>
<div>1</div>
<div>2</div>
</>
))
}
</script>
</html>
4-2 插值表达式
/*
* jsx规则:
* 1- 对JSX进行解析时,如果遇到<开头,那么<的内容首字母小写,会被作为HTML标签处理,如果<的内容首字母大写会被作为组件处理。
* 2- 对JSX进行解析时,如果遇到{开头,那么会被作为JS表达式去处理,并输出其表达式的结果。
* {}写入的内容只支持表达式,不支持JS语法。
* js语句:
* if 条件语句
* for 循环语句
* switch 选择语句
* js表达式:一个表达式会对应一个结果,表达式其实是一种特殊的JS语句
* 三元表达式
* 变量
* 常量
* fn()
* */
const root = ReactDOM.createRoot(document.querySelector("#root"));
let n = 3;
let str = "字符串";
let arr = [1,2,3,4];// 通过表达式输出为 1234
// 不允许将对象作为插值表达式
let obj = {
a:1,
b:2
}
// 注意:插值表达式中如果写入的是true,false,undefined,null不会渲染任何内容
root.render((
<div>
<p>1</p>
<p>{2}</p>
<p>{n}</p>
<p>{str}</p>
<p>{"str"}</p>
<p>{arr}</p>
{/*<p>{obj}</p>*/}
<p>{undefined}</p>
<p>{true}</p>
<p>{false}</p>
<p>{null}</p>
</div>
))
4-3- 条件渲染
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// 三元表达式来处理
// let sex = 1;
// let bol = false;
// root.render((
// <div>{sex===1?"男":"女"}|{bol?"真":"假"}</div>
// ))
}
{
// &&与运算符:该运算符左侧为true时,返回右侧的内容,如果为false,返回左侧内容。
// let bol = false;
// root.render((
// <div>
// {
// bol && "今天天气不错"
// }
// </div>
// ))
}
{
// ||或运算符:该运算符与&&运算符正好相反。左侧为false才会渲染右侧的值。
// let bol = true;
// root.render((
// <div>
// {
// bol || "周杰伦"
// }
// </div>
// ))
}
{
function fn(age){
if(age>60) return "老年";
if(age>30) return "中间";
if(age>18) return "青年";
if(age>12) return "少年";
if(age>6) return "儿童";
return "婴儿"
}
root.render((
<div>
<p>{fn(10)}</p>
<p>{fn(40)}</p>
<p>{fn(80)}</p>
<p>{fn(1)}</p>
</div>
))
}
4-4 属性渲染
const root = ReactDOM.createRoot(document.querySelector("#root"));
let imgSrc = "https://t7.baidu.com/it/u=3332251293,4211134448&fm=193&f=GIF";
let imgSrc2 = "https://t7.baidu.com/it/u=3505622605,280817994&fm=193&f=GIF";
const width = 100;
const bol = false;
root.render((
<div>
<img width="200" src="https://t7.baidu.com/it/u=1951548898,3927145&fm=193&f=GIF" alt=""/>
<img width={width} src={imgSrc} alt=""/>
<img width={width} src={bol ? imgSrc : imgSrc2} alt=""/>
</div>
))
4-5- className
const root = ReactDOM.createRoot(document.querySelector("#root"));
const bg = "bg";
const one = "cl";
const arr = ["bg","cl"];// 数组作为属性进行展示时是有逗号的,直接渲染是不带逗号
root.render((
<div>
<p className="bg">1</p>
<p className="cl">2</p>
<p className="bg cl">3</p>
<p className={"bg"}>4</p>
<p className={"cl"}>5</p>
<p className={"bg cl"}>6</p>
<p className={bg}>7</p>
<p className={one}>8</p>
<p>{arr}</p>
<p className={arr}>10</p>
</div>
))
4-6- style
// style的属性值必须要设置为对象
const root = ReactDOM.createRoot(document.querySelector("#root"));
const my ={
width:"200px",
height:"300px",
background:"green"
}
root.render((
<div>
{/*以下是一个错误示例*/}
{
// <div style="width:100px;height:100px;background:red;"></div>
}
<div style={{
width:"100px",
height:"200px",
background:"red"
}}></div>
<div style={my}></div>
</div>
))
4-7- 列表渲染
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// 1- 数组可以直接展开
// const arr = [1,2,3,4];
// root.render((
// <div>{arr}</div>
// ))
}
{
// 2- 数组内可以将JSX作为元素
// const arr = [<h3>1</h3>,<h3>2</h3>,<h3>3</h3>];
// root.render((
// <div>{arr}</div>
// ))
}
{
// 3- 通过ajax得到一些数据
// const newsList = [
// {
// newsTitle:"第一篇新闻"
// },{
// newsTitle:"第二篇新闻"
// }
// ]
// 3-1- forEach一
// const arr = [];
// newsList.forEach(item=>{
// arr.push(<p>{item.newsTitle}</p>)
// })
// root.render((
// <div>{arr}</div>
// ))
// 3-2- forEach二
// const arr = [];
// newsList.forEach(item=>arr.push(<p>{item.newsTitle}</p>))
// root.render((
// <div>{arr}</div>
// ))
// 3-2- forEach三
// const arr = [];
// newsList.forEach(item=>arr.push((
// <p>
// <a>{item.newsTitle}</a>
// </p>
// )))
// root.render((
// <div>{arr}</div>
// ))
}
{
// 4- 通过map渲染列表
// const newsList = [
// {
// newsTitle:"第一篇新闻"
// },{
// newsTitle:"第二篇新闻"
// }
// ];
// // 4-1- 可以先定义再输出,{}
// const arr = newsList.map(item=>{
// const {newsTitle} =item;
// return (
// <p>{newsTitle}</p>
// )
// })
// root.render((
// <div>{arr}</div>
// ))
// 4-2- 可以先定义再输出,()优点:简捷,缺点:无法写逻辑
// const arr = newsList.map(item=>(
// <p>{item.newsTitle}</p>
// ))
// root.render((
// <div>{arr}</div>
// ))
// 4-3- 可以先定义再输出,返回的内容有且只能是一个根元素{}
// const arr = newsList.map((item,index)=>{
// return (
// <>
// <div>{index}</div>
// <div>{item.newsTitle}</div>
// </>
// )
// })
// root.render(<div>{arr}</div>)
// 4-4- 可以先定义再输出,返回的内容有且只能是一个根元素()
// const arr = newsList.map((item,index)=>(
// <>
// <div>{index}</div>
// <div>{item.newsTitle}</div>
// </>
// ))
// root.render(<div>{arr}</div>)
}
{
const newsList = [
{
newsTitle:"第一篇新闻"
},{
newsTitle:"第二篇新闻"
}
];
// 5- 可以直接通过map输出
root.render((
<div>
{
newsList.map(item=>(
<p>{item.newsTitle}</p>
))
}
</div>
))
}
4-8 KEY
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// 在列表渲染时需要使用key,key是独一无二,不允许重复
const arr = [1, 2, 3, 4,4];
root.render((
<div>
{
arr.map((item,index)=>(
<div key={index}>{item}</div>
))
}
</div>
))
}
4-9- 注释
// 1- 在非属性中,注释要写到花括号内
// 2- 在属性中,注释不需要写到花括号内
// 生成react元素(虚拟DOM元素)(jsx): javascript xml
// class===>className
// style===>{}fontSize
const vDOM = (
<div>
{
// 这是单行注释
}
<p>1</p>
{
/*
* 多
* 行
* 注
* 释
* */
}
<p>2</p>
{/*注释*/}
<p>3</p>
<div
// 注释
id={"one"}
/*注释*/
className="a"
data-id={"my"}>div</div>
</div>
)
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(vDOM);
4-10 原生事件复习
- 如何增加事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="http://www.baidu.com">百度</a>
<a href="http://www.alibaba.com" onclick="go(event,this)">阿里</a>
</body>
<script>
// 为元素增加事件的两种试:
// 1- 先获取元素,然后为元素增加事件
document.querySelector("a").onclick = function (e) {
alert(this.innerText)
// return false;// 取消默认行为
e.preventDefault();// 取消默认行为
}
// 2- 先定义一个函数,然后在DOM元素中增加属性”on事件名=‘事件处理函数()’“
function go(e,_this){
// alert(this);
// alert(e);
alert(_this.innerText)
// return false;// 无法取消默认行为
e.preventDefault();// 可以取消默认行为
}
</script>
</html>
- 事件有一些注意事项(this)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button>0</button>
<button>1</button>
<button onclick="obj.fn2()">2</button>
<button onclick="fn3()">3</button>
<button onclick="obj.fn4()">4</button>
<button>5</button>
<button onclick="fn6()">6</button>
<button onclick="obj.fn7()">7</button>
<button onclick="obj.fn8()">8</button>
<button onclick="box.fn9()">9</button>
<button onclick="fn10()">10</button>
<button onclick="fn11()">11</button>
</body>
<script>
// "use strict";
// 关注事件当中this代表的是什么
const btns = document.querySelectorAll("button");
btns[0].onclick = function(){
console.log(this);// <button>0</button>
}
btns[1].onclick = ()=>{
console.log(this);// window
}
const obj = {
fn2(){
console.log(this===obj);// obj
},
fn3(){
console.log(this);// window
},
fn4:()=>{
console.log(this);// window
},
fn7(){
"use strict"
console.log(this);// obj
},
fn8:()=>{
"use strict"
console.log(this);//window
},
fn11(){
"use strict"
console.log(this);
}
}
const fn3 = obj.fn3;
const fn11= obj.fn11;
btns[5].onclick = function(){
"use strict"
console.log(this);// <button>5</button>
}
function fn6(){
"use strict"
console.log(this);// undefined
}
class Box{
fn9(){
console.log(this===box);// box
}
fn10(){
console.log(this);// undefined
}
}
const box = new Box()
const fn10 = box.fn10;
</script>
</html>
4-11- jsx中的事件
// jsx中定义事件与原生中定义事件的区别
// 1- 原生中定义事件全部小写(onclick),jsx中需要使用驼峰命名法(onClick)
// 2- 原生中事件属性的值是一个字符串,而JSX中必须是一个函数。
const root = ReactDOM.createRoot(document.querySelector("#root"));
function fn1(){
console.log(this);// undefined
}
const fn2 = ()=>{
console.log("fn2",this);// fn2 undefined
}
root.render((
<div>
<button onClick={fn1}>点我1</button>
<button onClick={fn2}>点我2</button>
<button onClick={()=>{
console.log(this);// undefined
}}>点我3</button>
<button onClick={function(){
console.log(this);// undefined
}}>点我4</button>
</div>
))
// 不允许这么干:
// const btn = document.querySelector("button");
// console.log(btn);// null
// btn.onclick = function(){
// console.log(1);
// }
五- 类组件(了解)
React当中的组件分为:类组件(复杂组件,状态组件)与函数组件(简单组件,非状态组件)
类组件:通过class定义的组件称为类组件
函数组件:通过function定义的组件称为函数组件。
组件:组件是页面当中的一部分,一个页面可以由若干个组件组成。
1- 组件可以被多次使用。
2- 可以将功能进行抽离。在页面当中不必写过多的代码,可以提升代码阅读性。
组件的使用:
jsx进行解析时,如果遇到<,<的内容是以大写字母开头,会作为组件来使用。组件是需要进行定义的。
5.1- 类组件的基本用法
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 在定义类组件时,与React的约定:
// 1- 的名字即是组件的名字,组件的名字首字母必须要大写
// 2- 在定义类组件时,必须继承React.Component
// 3- 类组件中必须要有render函数
// 4- render函数的返回值即是要渲染的内容
// 5- 组件在使用时,可以采用<组件名/>, <组件名></组件名>
// 6- 组件可以使用多次
{
class App extends React.Component {
render() {
// return "字符串"
// return (
// <div>
// <p>jsx</p>
// </div>
// )
// 如果返回的JSX有且只能是一个根元素
return (
<>
<div>1</div>
<div>2</div>
</>
)
}
}
root.render((
<div>
<App/>
<App></App>
</div>
))
}
5.3- 组合组件(组件嵌套)
const root = ReactDOM.createRoot(document.querySelector("#root"));
const {Component} = React;
class Child extends Component{
render(){
return (
<div>
<h3>Child</h3>
</div>
)
}
}
// App嵌套了组件Child
// App组件是Child组件的父组件
// Child组件是App组件的子组件
class App extends React.Component {
render() {
return (
<div>
<h3>App</h3>
<Child/>
</div>
)
}
}
root.render((
<>
<App/>
<Child></Child>
</>
))
5.4- 组件传递属性
const root = ReactDOM.createRoot(document.querySelector("#root"));
// 父子传递参数:通过属性。
// 父组件需要传递:可以将参数作为调用子组件时的属性
// 子组件需要接收:this.props
class App extends React.Component{
info={
userName:"wangwu",
age:900
}
render(){
return (
<div>
<h3>App</h3>
<Child userName={"zhangsan"} age={12}></Child>
<Child userName={"lisi"} age={800}></Child>
<Child userName={this.info.userName} age={this.info.age}></Child>
{/*以下写法与上方代码等值*/}
<Child {...this.info}></Child>
</div>
)
}
}
class Child extends React.Component{
// constructor(props) {
// super(props);
// console.log(this.props);// 得到的是一个对象,对象当中的属性即是父级设置的属性 {userName:"zhangsan",age:12}
// }
render(){
return (
<div>
<h3>Child</h3>
<p>userName:{this.props.userName}</p>
<p>age:{this.props.age}</p>
</div>
)
}
}
root.render(<App/>)
5.5 属性不允许直接修改
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
render(){
// this.props.userName = "lisi";// 只读属性不允许被修改
let {userName,age} = this.props;
userName = "lisi";
return (
<div>
<h3>APP</h3>
<p>userName:{this.props.userName}|{userName}</p>
<p>age:{this.props.age}|{age}</p>
</div>
)
}
}
root.render(<App age={12} userName={"zhangsan"}/>)
5.6 属性是可以进行类型限制的
// 属性是可以进行类型的限制的。
// 1- 引入prop-types.js,可以通过该文件对组件接收的属性进行类型限制
// 2- 为类组件增加静态属性propTypes(限制类型),defaultProps(可以设置默认值)
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
static propTypes = {
// 类型必须是字符串
userName: PropTypes.string,
// 类型必须是数字,且不允许为空
age:PropTypes.number.isRequired,
sex:PropTypes.string.isRequired
// func:函数 object:对象 array:数组
}
static defaultProps = {
// 可以设置默认值
sex:"男"
}
render(){
return (
<div>
<h3>APP</h3>
<p>userName:{this.props.userName}</p>
<p>age:{this.props.age}</p>
<p>sex:{this.props.sex}</p>
</div>
)
}
}
root.render(<App age={12} userName={"zhangsan"}/>)
5.7- 传递参数的DEMO
const root = ReactDOM.createRoot(document.querySelector("#root"));
const boyBook = [
{
id:1,
bookName:"天龙八部",
author:"金庸"
},{
id:2,
bookName: "斗罗大陆",
author: "唐家三少"
}
]
const girlBook = [
{
id:11,
bookName:"还珠格格",
author:"琼瑶"
},{
id:12,
bookName: "情深深雨朦朦",
author: "琼瑶"
}
]
{
// 不使用组件
// root.render((
// <div>
// <div>
// <h3 style={{color:"blue"}}>男生小说</h3>
// {
// boyBook.map(item=>(
// <p key={item.id}>{item.author}:《{item.bookName}》</p>
// ))
// }
// </div>
// <div>
// <h3 style={{color:"pink"}}>女小说</h3>
// {
// girlBook.map(item=>(
// <p key={item.id}>{item.author}:《{item.bookName}》</p>
// ))
// }
// </div>
// </div>
// ))
}
{
// 使用组件
class BookList extends React.Component{
static propTypes = {
bookInfo:PropTypes.array.isRequired,
title:PropTypes.string.isRequired
}
render(){
return (
<div>
<h3 style={{color:this.props.color}}>{this.props.title}</h3>
{
this.props.bookInfo.map(item=>(
<p key={item.id}>{item.author}:《{item.bookName}》</p>
))
}
</div>
)
}
}
root.render((
<div>
<BookList bookInfo={boyBook} color={"blue"} title={"男生小说"}></BookList>
<BookList bookInfo={girlBook} color={"pink"} title={"女生小说"}></BookList>
</div>
))
}
5.8- 类组件事件this指向undefined的问题
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
clickHandler(){
console.log(this);// undefined
}
render(){
return (
<div>
<h3>APP</h3>
<button onClick={function(){
console.log(this);// undefined
}}>点我呀</button>
<button onClick={this.clickHandler}>点我呀2</button>
</div>
)
}
}
root.render((
<App></App>
))
5.9- 解决类组件事件this指向undefined问题的四个方法
-
将函数直接设置为箭头函数
// 1- 将函数直接设置为箭头函数 // 优点:方便 // 缺点:不能复用,如果逻辑代码较多,阅读不方便 const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component{ render(){ // console.log("render",this); return ( <div> <h3>App</h3> <button onClick={(e)=>{ console.log(this,e.target.innerText); }}>你过来呀</button> </div> ) } } root.render(( <App></App> )) -
可以将箭头函数在类中进行定义
// 2- 可以将箭头函数在类中进行定义 // 优点:可以被复用,代码多时也易于阅读 // 缺点:函数在实例中定义,如果组件被多次使用,会占用内存。 const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component{ clickHandler=(e)=>{ console.log(this,e.target.innerText); } render(){ // console.log("render",this); return ( <div> <h3>App</h3> <button onClick={this.clickHandler}>你过来呀</button> </div> ) } } root.render(( <App></App> )) -
可以通过bind将this进行指定(绑定)
// 3-建议:可以通过bind将this进行指定(绑定) // 优点:传递参数方便,可以复用 // 缺点:每次调用都要写bind const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component{ clickHandler(a,b,c,d,e){ console.log(this,e.target.innerText); } render(){ // console.log("render",this); return ( <div> <h3>App</h3> <button onClick={this.clickHandler.bind(this, 1, 2, 3,4)}>你过来呀</button> </div> ) } } root.render(( <App></App> )) -
在实例属性中进行绑定
// 4 在实例属性中进行绑定 // 缺点:组件被多次使用,每次使用均会在内存中创建实例属性clickHandler // 传递参数只能够进行一次指定,动态 指定参数不方便 // 优点:当你在使用函数时,不需要再次写bind const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component{ constructor(props) { super(props); this.clickHandler = this.clickHandler.bind(this,1,2,3,4); } clickHandler(a,b,c,d,e){ console.log(this,a,b,c,d,e.target.innerText); } render(){ // console.log("render",this); return ( <div> <h3>App</h3> <button onClick={this.clickHandler}>你过来呀</button> </div> ) } } root.render(( <App></App> ))
5-10 状态(state)
-
状态的定义与使用
// 属性(props):在父级定义,属性是只读的,不允许修改 // 状态(state):在组件中定义,是允许修改的。 const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component{ // 声明状态方式一 state = { num:100 } // constructor(props) { // super(props); // // 在实例属性中设置state,值必须是对象 // // 声明状态方式二 // // this.state = { // // num:0 // // } // } render(){ return ( <div> <h3>App</h3> <button onClick={()=>{ // 状态修改的正途是通过this.setState进行更改 // 一旦执行this.setState,那么会再次执行render,会重新生成虚拟DOM,与上一次生成的虚拟DOM进行比较,将不同点进行更新。 // 修改状态一: // 1- 状态通过setState是异步修改 // this.setState({ // num:this.state.num+1 // }) // console.log(this.state.num); // 2- this.setState第一个参数是对象,可以指定修改的状态。第二个参数是回调函数。 // 注意:不要用setState以外方法去更改状态 // this.setState({ // num:this.state.num+1 // },function(){ // // 当数据更新完毕且界面渲染完成之后执行回调。 // console.log(this.state.num,document.querySelector("button").innerText); // }) // console.log(this.state.num); // 修改状态二: this.setState(function(prevState,props){ // console.log(prevState,props); console.log(this.state === prevState,this.props === props);//true true return { num:prevState.num+1 } },()=>{ console.log("回调函数",this.state.num) }) console.log(this.state.num); }}>{this.state.num}</button> </div> ) } } root.render(( <App a={1} b={2}></App> )) -
同步情况:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <!-- <script src="./lib/react.development.js"></script>--> <!-- <script src="./lib/react-dom.development.js"></script>--> <script src="./lib/babel.min.js"></script> </head> <body> <div id="root"></div> </body> <script type="text/babel"> // 注意:在18之后都是异步,在18之前如果将this.setState写到定时器或promise中为同步的。 class App extends React.Component{ state ={ userName:"zhangsan" } render(){ return ( <div> <h3>app</h3> <button onClick={()=>{ // 异步情况: this.setState({ userName:this.state.userName+"!" }) console.log("异步",this.state.userName); // 定时器或promise // setTimeout(()=>{ // this.setState({ // userName:this.state.userName+"!" // }) // console.log(this.state.userName); // }) // Promise.resolve().then(value=>{ // this.setState({ // userName:this.state.userName+"!" // }) // console.log(this.state.userName); // }) }}>{this.state.userName}</button> </div> ) } } // console.log(ReactDOM) ReactDOM.render(<App/>,document.querySelector("#root")); </script> </html>
5-11 ref
通过ref的方式可以获取真实的DOM.
-
与字符串结合使用
const root = ReactDOM.createRoot(document.querySelector("#root")); // ref使用方式一: // 1- 可以为要操作的React元素上增加ref属性,其值是一个字符串 // 2- 可以通过this.refs.字符串可以获得其真实DOM // 3- ref与组件结合使用时,可以获得组件实例。从而可以实现组件之间的通讯。 // 注意:ref与组件结合使用时,那么在组件中通过this.props是得不到ref的。如果要向子组件传递参数,参数名不要写成ref. class App extends React.Component{ render(){ return ( <div> <h3 ref={"title"}>App</h3> <input ref={"keyword"} type="text"/> <button onClick={()=>{ // 如果React元素上没有ref,那么this.refs的值为{} // console.log(this.refs);// {} // 在React元素中增加ref,this.refs会多一个属性,属性的名字即是React元素中ref的属性值。 // console.log(this.refs);// {keyword:} // console.log(this.refs.keyword === document.querySelector("input")); // console.log(this.refs);// {title:xxx,keyword:xxx} // console.log(this.refs.title.innerText);// App // console.log(this.refs.keyword.value) // console.log(this.refs.child)// 得到的是子组件实例 console.log(this.refs.child.userName); console.log(this.refs.child.state.age); this.refs.child.setState({ age:this.refs.child.state.age+3 }) }}>点我呀</button> <Child ref={"child"} a={1}></Child> </div> ) } } class Child extends React.Component{ userName="zhangsan"; state={ age:12 } render(){ console.log("child",this.props) return ( <div> <h3>Child</h3> <button onClick={()=>{ this.setState({ age:this.state.age+2 }) }}>{this.state.age}</button> </div> ) } } root.render(( <App></App> )) -
ref与箭头函数结合使用
// ref使用方式二: // React元素的属性ref的值如果是一个箭头函数,那么不可以通过this.refs获取其真实DOM // ref的值是箭头函数的特点: // 1- 当组件在挂载时,箭头函数会执行。 // 2- 箭头函数接收的参数即是真实的DOM. // 3- 在使用时,需要将接收到的DOM作为当前实例的属性。 const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component { render() { return ( <div> <h3 ref={el=>this.title=el}>App</h3> <input type="text" ref={el=>{ // console.log("执行",el===document.querySelector("input")) this.keyword = el;// 为当前实例增加属性keyword,值为真实DOM }}/> <button onClick={()=>{ console.log(this.keyword.value); console.log(this.title.innerText); console.log(this.child.userName); }}>点我呀</button> <Child ref={el=>this.child=el}></Child> </div> ) } } class Child extends React.Component{ userName="zhangsan"; render(){ return ( <div> <h3>CHILD</h3> </div> ) } } root.render(( <App></App> )) -
ref与React.createRef结合使用(推荐)
// ref使用方式三:React.createRef // 1- 自定义一个实例属性,其值为React.createRef() // 2- 为React元素增加ref属性,其值设置为自定义的实例属性。 // 3- 可以通过实例属性.current得到真实DOM const root = ReactDOM.createRoot(document.querySelector("#root")); const {createRef} = React; class App extends React.Component { keyword = React.createRef(); title=createRef(); child = createRef(); render() { console.log(this.keyword);// {current} return ( <div> <h3 ref={this.title}>App</h3> <input ref={this.keyword} type="text" /> <button onClick={()=>{ // console.log(this.keyword.current===document.querySelector("input")) console.log(this.keyword.current.value); console.log(this.title.current.innerText); console.log(this.child.current.userName); this.keyword.current.value = 100; }}>点我呀</button> <Child ref={this.child}></Child> </div> ) } } class Child extends React.Component{ userName="zhangsan"; render(){ return ( <div> <h3>CHILD</h3> </div> ) } } root.render(( <App></App> ))
5.12- 受控组件
// 表单当中的元素在使用时会划分为两种:受控组件,非受控组件。
// 如果在表单元素中使用value,checked那么会受其值的控制。
// 一旦组件受控,需要结合onChnage事件来更改value值
// 对于表单的两种操作:1- 设置表单值,2-收集表单数据
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
state = {
userName:"zhangsan",
passWord:"123456",
city:2,
sex:"1",
hobby:["1","3"]
}
changeHandler(e){
let data = e.target.value;
// 优化
if(e.target.type === "checkbox"){
data = [...this.state[e.target.name]];// 复制hobby
const index = data.findIndex(v=>v === e.target.value);
if(index>-1) data.splice(index,1)
else data.push(e.target.value);
}
this.setState({
[e.target.name]:data
})
/*思路:
* 除了多选之外,以下代码都支持
* 判断元素是否为多选*/
// console.log(e.target.type);
// if(e.target.type === "checkbox"){
// // 判断状态当中是否包含e.target.value
// // 如果包含,在数组中移除该数据
// // 如果不包含,在数组中增加该数据。
// const data = [...this.state[e.target.name]];// 复制hobby
// const index = data.findIndex(v=>v === e.target.value);
// if(index>-1){
// data.splice(index,1);
// // 如果包含,在数组中移除该数据
// this.setState({
// [e.target.name]:data
// })
// }else{
// // 如果不包含,在数组中增加该数据。
// data.push(e.target.value);
// this.setState({
// [e.target.name]:data
// })
// }
// }else{
// this.setState({
// [e.target.name]:e.target.value
// })
// }
}
submitHandler(e){
e.preventDefault();// 移除默认行为。
console.log("用户名",this.state.userName);
console.log("密码",this.state.passWord);
console.log("城市",this.state.city);
console.log("性别",this.state.sex);
console.log("爱好",this.state.hobby)
}
render(){
return (
<div>
<h3>App</h3>
<form onSubmit={this.submitHandler.bind(this)}>
<p>用户名:<input name={"userName"} onChange={this.changeHandler.bind(this)} type="text" value={this.state.userName}/></p>
<p>密码:<input name={"passWord"} onChange={this.changeHandler.bind(this)} type="password" value={this.state.passWord}/></p>
<p>城市:
<select onChange={this.changeHandler.bind(this)} name="city" value={this.state.city}>
<option value="1">上海</option>
<option value="2">北京</option>
<option value="3">武汉</option>
<option value="4">西安</option>
<option value="5">深圳</option>
<option value="6">南白滩</option>
</select>
</p>
<p>性别:
<input name="sex" onChange={this.changeHandler.bind(this)} checked={this.state.sex==="1"} type="radio" value={"1"}/>男
<input name="sex" onChange={this.changeHandler.bind(this)} checked={this.state.sex==="2"} type="radio" value={"2"}/>女
</p>
<p>
爱好:
<input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("1")} value={"1"}/>学习
<input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("2")} value={"2"}/>抽烟
<input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("3")} value={"3"}/>喝酒
<input name={"hobby"} onChange={this.changeHandler.bind(this)} type="checkbox" checked={this.state.hobby.includes("4")} value={"4"}/>烫头
</p>
<button>提交</button>
</form>
</div>
)
}
}
root.render((
<App></App>
))
5.13- 非受控组件
const root = ReactDOM.createRoot(document.querySelector("#root"));
const {createRef} = React;
class App extends React.Component{
state = {
userName:"wangwu",
passWord:"123456",
city:"6",
sex:"2",
hobby:["1","4"]
}
userName = createRef();
passWord = createRef();
city = createRef();
sex = [createRef(),createRef()];
hobby = [createRef(),createRef(),createRef(),createRef()];
submitHandler(e){
e.preventDefault();
console.log("用户名",this.userName.current.value);
console.log("密码",this.passWord.current.value);
console.log("城市",this.city.current.value);
console.log("性别",this.sex.find(v=>v.current.checked).current.value);
console.log("爱好",this.hobby.filter(v=>v.current.checked).map(v=>v.current.value))
}
render(){
return (
<div>
<h3>App</h3>
<form onSubmit={this.submitHandler.bind(this)}>
<p>用户名:<input ref={this.userName} name={"userName"} defaultValue={this.state.userName} type="text" /></p>
<p>密码:<input ref={this.passWord} name={"passWord"} defaultValue={this.state.passWord} type="password"/></p>
<p>城市:
<select ref={this.city} name="city" defaultValue={this.state.city} >
<option value="1">上海</option>
<option value="2">北京</option>
<option value="3">武汉</option>
<option value="4">西安</option>
<option value="5">深圳</option>
<option value="6">南白滩</option>
</select>
</p>
<p>性别:
<input ref={this.sex[0]} name="sex" defaultChecked={this.state.sex==="1"} type="radio" value={"1"}/>男
<input ref={this.sex[1]} name="sex" defaultChecked={this.state.sex==="2"} type="radio" value={"2"}/>女
</p>
<p>
爱好:
<input name={"hobby"} ref={this.hobby[0]} defaultChecked={this.state.hobby.includes("1")} type="checkbox" value={"1"}/>学习
<input name={"hobby"} ref={this.hobby[1]} defaultChecked={this.state.hobby.includes("2")} type="checkbox" value={"2"}/>抽烟
<input name={"hobby"} ref={this.hobby[2]} defaultChecked={this.state.hobby.includes("3")} type="checkbox" value={"3"}/>喝酒
<input name={"hobby"} ref={this.hobby[3]} defaultChecked={this.state.hobby.includes("4")} type="checkbox" value={"4"}/>烫头
</p>
<button>提交</button>
</form>
</div>
)
}
}
root.render((
<App></App>
))
六- 生命周期
生命周期指的是类组件。类组件从创建到销毁的过程称其为该组件的生命周期。
在组件的生命周期中,所暴露出来(在阶段中所自动执行)的函数称为钩子函数。
6.1- 生命周期旧
6.1.1- 挂载阶段
// 在组件挂载阶段暴露出来的钩子函数,除 render以外,均只运行一次。
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
state = {
num:1
}
constructor(props) {
super(props);
// 实例化
console.log("1-constructor");
}
UNSAFE_componentWillMount(){
// 在挂载之前
console.log("2-UNSAFE_componentWillMount",document.querySelector("h3"),this.state.num);
this.state.num = 100;
}
render(){
// 指定要挂载的内容
console.log("3-render");
return (
<div>
<h3>App</h3>
<button onClick={()=>{
this.state.num = 1000;
}}>{this.state.num}</button>
</div>
)
}
componentDidMount(){
// 挂载结束之后
console.log("4-componentDidMount",document.querySelector("h3"))
}
}
root.render((
<App></App>
))
6.1.2- 更新属性阶段
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
state = {
a:1,
b:2
}
render(){
return (
<div>
<h3>App</h3>
<button onClick={()=>{
this.setState({
a:this.state.a+1
})
}}>更改状态a</button>
<Child {...this.state}></Child>
</div>
)
}
}
class Child extends React.Component{
// constructor(props) {
// super(props);
// // console.log("1-constructor");
// }
// UNSAFE_componentWillMount(){
// console.log("2-UNSAFE_componentWillMount");
// }
UNSAFE_componentWillReceiveProps(nextProps){
console.log("1-UNSAFE_componentWillReceiveProps",this.props.a,nextProps.a)
}
shouldComponentUpdate(nextProps){
// 当要更新属性时,执行到该钩子,this.props还未更新。接收的形参nextProps是即将要更新的属性
console.log("2-shouldComponentUpdate",this.props.a,nextProps.a)
if(nextProps.a<5) return true;
return false;
}
UNSAFE_componentWillUpdate(nextProps){
console.log("3-UNSAFE_componentWillUpdate",this.props.a,nextProps.a)
}
render(){
console.log("4-render",this.props.a)
return (
<div>
<h3>Child</h3>
<p>a:{this.props.a}</p>
<p>b:{this.props.b}</p>
</div>
)
}
componentDidUpdate(prevProps){
console.log("5-componentDidUpdate",this.props.a,prevProps.a);
}
// componentDidMount(){
// console.log("4-componentDidMount")
// }
}
root.render((
<App></App>
))
6.1.3- 更新状态阶段
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
state = {
num:1
}
shouldComponentUpdate(nextProps,nextState){
// 状态还未更新。nextState是即将要更新的状态。
// console.log(this.state.num,nextState.num)
console.log("1-shouldComponentUpdate")
if(nextState.num<5) return true;
return false;
}
UNSAFE_componentWillUpdate(nextProps,nextState){
console.log("2-UNSAFE_componentWillUpdate",this.state.num,nextState.num)
}
render(){
console.log("3-render",this.state.num)
return (
<div>
<h3>App</h3>
<button onClick={()=>{
// this.setState({
// num:this.state.num+1
// })
this.state.num = 100;
this.forceUpdate();// 强制更新
}}>{this.state.num}</button>
</div>
)
}
componentDidUpdate(prevProps,prevState){
console.log("4-componentDidUpdate",this.state.num,prevState.num)
}
}
root.render((
<App></App>
))
6.1.4- 卸载阶段
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="./lib/react.development.js"></script>
<script src="./lib/react-dom.development.js"></script>
<script src="./lib/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
state = {
isShow:true
}
changeIsShow(){
this.setState({
isShow:!this.state.isShow
})
}
render(){
return (
<div>
<button onClick={this.changeIsShow.bind(this)}>显示与隐藏</button>
{
this.state.isShow && <Child/>
}
</div>
)
}
}
class Child extends React.Component{
state={
num:1
}
constructor(props) {
super(props);
console.log("挂载1-》constructor")
}
UNSAFE_componentWillMount(){
console.log("挂载2-》UNSAFE_componentWillMount")
}
render(){
console.log("挂载3-》render")
return (
<div style={{width:200,height:200,border:"1px solid yellow"}}>
<h3>{this.state.num}</h3>
</div>
)
}
componentDidMount(){
console.log("挂载4->componentDidMount");
this.timer = setInterval(()=>{
console.log("setInterval",this.state.num);
this.setState({
num:this.state.num+1
})
},1000)
}
componentWillUnmount(){
clearInterval(this.timer);// 清除定时器
console.log("componentWillUnmount,当组件卸载之前执行");
}
}
root.render((
<App></App>
))
</script>
</html>
6.2- 生命周期新
const root = ReactDOM.createRoot(document.querySelector("#root"));
class App extends React.Component{
state = {
userName:"zhangsan"
}
render(){
return (
<div>
<button onClick={()=>{
this.setState({
userName:this.state.userName+"!"
})
}}>点我</button>
<Child userName={this.state.userName}></Child>
</div>
)
}
}
class Child extends React.Component{
state = {
a:1,
b:2,
userName:"lisi"
}
// 作用:可以将接收到属性与当前的数据状态进行合并。
// 在什么时候执行:
// 1- 组件挂载时会运行。
// 2- 父组件执行render后会执行。
// 3- 在当前组件中执行this.setState
// 4- 强制更新
// 特点:
// 1- 组件中必须定义state
// 2- 必须要有返回值,且返回值必须是对象或null
// 3- 如果返回的对象中的属性与当前状态的属性冲突,那么会将数据状态进行覆盖
// 4- getDerivedStateFromProps是一个静态方法,该方法的this是undefined
static getDerivedStateFromProps(props){
console.log(props,this);
return {
// a:100,
c:3,
d:4,
...props
}
}
render(){
return (
<div>
<h3>Child</h3>
<button onClick={()=>{
// this.forceUpdate();// 强制更新
this.setState({
a:this.state.a+1
})
}}>{this.state.a}</button>
</div>
)
}
// 注意:
// 1- getSnapshotBeforeUpdate需要与componentDidUpdate结合使用
// 2- getSnapshotBeforeUpdate它的返回值,会作为componentDidUpdate的第三个参数
// 3- 执行该钩子时,状态值已经发生变化
// 4- 当前的DOM还未更新。
// 在此处可以生成日志信息。
getSnapshotBeforeUpdate(prevProps,prevState){
// prevState:状态更新之前的值
// this.state:当前状态值
console.log(prevState.a,this.state.a);
console.log(document.querySelectorAll("button")[1].innerText)
return 100;
}
componentDidUpdate(prevProps,prevState,snapshot){
console.log("更新完毕!componentDidUpdate",snapshot)
}
}
root.render((
<App></App>
))
五、react-cli 脚手架(重点)
5.1- 介绍
- React脚手架: 用来帮助程序员快速创建一个基于React库的模板项目。
- 脚手架是开发现代Web应用的必备。
- 充分使用Webpack、Babel、ESLint等工具辅助项目开发。
- 零配置,无需手动配置繁琐的工具即可使用。
- 让程序可以关注业务,而不是工具配置
5.2- 创建项目并启动
-
创建项目:注意:项目名称不能包含大写字母;不要写中文;项目所在的目录不允许出现中文以及特殊字符。
npx create-react-app one #创建一个名字为one的项目 -
成功界面
-
启动项目
cd one; npm start # 启动项目 -
npx命令介绍:
-
npx是 npm v5.2.0引入的一条命令
-
目的:提升包内提供的命令行工具的使用体验。
-
之前:需要全局安装脚手架包,再使用这个包中提供的命令。
npm install -g create-react-app// 全局安装一次 create-react-app hello-react //创建项目 cd hello-react npm start -
现在:无需安装全局安装脚手架包,就可以直接使用这个包提供的命令。
-
-
安装vscode插件
5.3-react脚手架项目结构
- 项目结构:
目录结构分析:
one
|-public
| |-favicon.ico // 应用程序顶部的icon图标
| |-index.html // 应用程序index.html入口文件
| |-logo192.png // 被在mainfest.json中使用
| |-logo512.png // 被在mainfest.json中使用
| |-manifest.json // 和web app 配置相关
| |-robots.txt // 指定搜索引擎可以或者无法爬取哪些文件
|-src
| |-App.css // App组件相关样式
| |-App.js // App组件的代码文件
| |-App.tes.js // App组件的测试代码文件
| |-index.css // 全局的样式文件
| |-index.js // 整个应用程序的入口文件
| |-logo.svg // 启动项目,所看到的React图标
| |-reportWebVitals.js// 提供各种质量信号的统一指南
| |-setupTest.js // 测试初始文件
|-gitgnore // git排除文件配置
|-package.json// 对整个应用程序的描述:包括应用名称,版本号,依赖包,以及项目的启动打包等等
|-README.md // readme说明文档
5-4 初次使用脚手架
-
清除src下的所有文件
-
在src中增加index.js
import React from "react";// 引入React核心模块 import ReactDOM from "react-dom/client";// 用于操作DOM的模块 // 生成React容器,赋值给常量root const root = ReactDOM.createRoot(document.querySelector("#root")); class App extends React.Component{ render(){ return ( <div> <h3>App</h3> </div> ) } } root.render(<App/>)
5-5 使用组件
-
src->App.jsx
// 1- 先定义再导出 // import React from "react"; // class App extends React.Component{ // render(){ // return ( // <div> // <h3>App1</h3> // </div> // ) // } // } // export default App; // 2- 可以直接导出 // import React from "react"; // export default class App extends React.Component{ // render(){ // return ( // <div>App2</div> // ) // } // } // 3- 定义组件 // import React from "react"; // const {Component} = React; // export default class App extends Component{ // render(){ // return ( // <div>App3</div> // ) // } // } // 4- 定义组件(常见) // import React,{Component} from "react";// export // export default class App extends Component{ // render(){ // return ( // <div>App3</div> // ) // } // } // 5- 默认导出APP import React,{Component} from "react";// export export class My extends Component{ render(){ return ( <div>My</div> ) } } export default class App extends Component{ render(){ return ( <div>App3</div> ) } } -
src->index.js
import React from "react";// 引入React核心模块 import ReactDOM from "react-dom/client";// 用于操作DOM的模块 import App,{My} from "./App"; // 生成React容器,赋值给常量root const root = ReactDOM.createRoot(document.querySelector("#root")); root.render(( <> <App/> <My></My> </> ))
5-6 函数组件(重点)
5-6-1- 函数组件的基本使用
-
App.jsx
// vscode: rfc // webstorm:rsf(function) rsc(箭头函数) import React,{Fragment} from 'react'; const bookInfo = [{ id:1, bookName:"天龙八部", author:"金庸" },{ id:2, bookName: "圆月弯刀", author: "古龙" }]; function App(){ // return ( // <> // { // bookInfo.map(item=>( // <React.Fragment key={item.id}> // <h3>{item.author}</h3> // <p>{item.bookName}</p> // </React.Fragment> // )) // } // </> // ) return ( <> { bookInfo.map(item=>( <Fragment key={item.id}> <h3>{item.author}</h3> <p>{item.bookName}</p> </Fragment> )) } </> ) } export default App; -
src->index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
/*函数组件的约定:
* 约定1:函数组件是通过函数来定义的
* 约定2:函数的名字必须大写,函数的名字即是组件的名字
* 约定3:在使用时,将函数的名字作为标签名即可
* 约定4:函数必须要有返回值,返回的内容即是渲染的内容
* 约定5:函数如果返回的是null,undefined,false,true不会渲染内容
* 约定6:函数组件如果返回的是JSX,有且只能是一个根元素。*/
{
// 定义函数组件1
// function App(){
// // return null;
// // return false;
// // return true;
// // return undefined;
// return (
// <h3>App</h3>
// )
// }
// root.render(<App/>)
}
{
// 定义函数组件2(定义为箭头函数)
// const App = ()=>{
// return (
// <h3>App2</h3>
// )
// }
// root.render(<App/>)
}
{
// 定义的函数返回的内容有且只能是一个根元素,以下写法不正确
// function App(){
// return (
// <div>1</div>
// <div>2</div>
// )
// }
// root.render(<App/>)
}
{
// 可以通过包裹标签(<></>)解决上一个示例的问题
// function App(){
// return (
// <>
// <div>1</div>
// <div>2</div>
// </>
// )
// }
// root.render(<App/>)
}
{
// <></>是包裹标签,不会进行渲染。
// <></>在map时,不可以作为包裹标签。因为无法增加属性key
// React.Fragment也是包裹标签。
// React.Fragment与<></>的区别:React.Fragment可以用在map回调中,React.Fragment可以增加属性key
// const bookInfo = [{
// id:1,
// bookName:"天龙八部",
// author:"金庸"
// },{
// id:2,
// bookName: "圆月弯刀",
// author: "古龙"
// }];
// function App(){
// return (
// <>
// {
// bookInfo.map(item=>(
// <React.Fragment key={item.id}>
// <h3>{item.author}</h3>
// <p>{item.bookName}</p>
// </React.Fragment>
// ))
// }
// </>
// )
// }
// root.render(<App/>)
}
{
// 将函数组件放置到外部
root.render(<App/>)
}
5-6-2- 函数组件进行嵌套,组件也可以使用多次
import React from "react";
import ReactDOM from "react-dom/client";
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// 函数组件是可以多次使用的
// function App(){
// return (
// <div>
// <h3>App</h3>
// </div>
// )
// }
// root.render((
// <>
// <App/>
// <App/>
// <App/>
// <App/>
// <App/>
// </>
// ))
}
{
// 函数组件是可以进行嵌套的(组合)
function App(){
return (
<div>
<h3>App</h3>
<Child></Child>
</div>
)
}
function Child(){
return (
<div>
<h3>Child</h3>
</div>
)
}
root.render((
<>
<App/>
<Child/>
</>
))
}
5-6-3- 函数组件抽离
- src->index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import Child from "./Child";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render((
<>
<App></App>
<Child></Child>
</>
))
- src->App.jsx
import React from 'react';
import Child from "./Child";
function App(props) {
return (
<div>App
<Child></Child>
<Child></Child>
<Child></Child>
<Child></Child>
</div>
);
}
export default App;
- src->Child.jsx
import React from 'react';
function Child(props) {
return (
<div>Child</div>
);
}
export default Child;
5-6-4- 函数组件传递属性
- src->index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render(<App/>)
- src->App.jsx
import React from 'react';
import Child from "./Child";
function App(props) {
return (
<div>
<Child userName={"zhangsan"} age={12}></Child>
</div>
);
}
export default App;
- src->Child.jsx
import React from 'react';
// 可以通形参props来接收属性。形参的名字也可以自定义
// props的值是一个对象,所以可以进行解构赋值
function Child(props) {
// console.log(props);
// 将对象props解构赋值给常量userName,age
const {userName,age} = props;
return (
<div>Child
<p>userName:{props.userName}|{userName}</p>
<p>age:{props.age}|{age}</p>
</div>
);
}
export default Child;
5-6-5- 函数组件对属性类型限制
-
App.jsx
import React from 'react'; import Child from "./Child"; import My from "./My" const state = { userName:"zhangsan", age:12 } function App(props) { return ( <div> <Child userName={state.userName} age={state.age}></Child> <hr/> <Child {...state}></Child> <hr/> <Child age={18}></Child> <hr/> <My userName={"zhangsan"} age={18}></My> </div> ); } export default App; -
Child.jsx
import React from 'react';
import PropTypes from "prop-types";
// 函数可以直接增加属性。称为函数的静态属性
Child.propTypes = {
// userName属性必须是字符串,且不允许为空(undefined)
userName:PropTypes.string.isRequired,
// age属性必须是数字
age:PropTypes.number
// PropTypes.object 对象
// PropTypes.array 数组
// PropTypes.func 函数
}
// 可以设置默认值
Child.defaultProps = {
userName:"zhaoliu"
}
function Child(props) {
const {userName,age} = props;
return (
<div>Child
<p>userName:{props.userName}|{userName}</p>
<p>age:{props.age}|{age}</p>
</div>
);
}
export default Child;
- My.jsx(了解)
import React, {Component} from 'react';
import PropTypes from "prop-types";
class My extends Component {
// static propTypes = {
// userName:PropTypes.string.isRequired,
// age:PropTypes.number.isRequired
// }
// static defaultProps = {
// userName:"zhangpeiyue",
// age:16
// }
render() {
return (
<div>
<p>userName:{this.props.userName}</p>
<p>age:{this.props.age}</p>
</div>
);
}
}
My.propTypes = {
userName:PropTypes.string.isRequired,
age:PropTypes.number.isRequired
}
My.defaultProps = {
userName:"zhangpeiyue",
age:16
}
export default My;
5-6-6- 渲染属性(render-props)
- app.jsx
import React, {Fragment} from 'react';
import BookList from "./components/BookList";
// 渲染属性:向组件传递的是一个函数,函数的名字一般设置为render,render会返回JSX
// 接收属性的组件中可以直接调用render
function App(props) {
const boyBook = [
{
id:1,
bookName:"大明:我重生成了朱允炆",
bookImg:"https://static.zongheng.com/upload/s_image/cover/5a/84/5a84519422b2ab100f5bf318d8874a56.jpeg"
},
{
id:2,
bookName:"模拟修仙传",
bookImg:"https://static.zongheng.com/upload/s_image/cover/a3/5e/a35e28be78d8f6832f62386eb0abfe0d.jpeg"
}
];
const girlBook = [
{
id:11,
bookName:"王妃重生后忙着搞事业",
bookImg:"https://static.zongheng.com/upload/s_image/cover/b6/0d/b60d0b4f52d17b6e579de4c218883597.jpeg"
},
{
id:12,
bookName:"权宠娇娘",
bookImg:"https://static.zongheng.com/upload/s_image/cover/99/57/995725bacaef47e5a481d9b58555c261.png"
},
]
return (
<div>
<BookList render={()=>(
<h3 style={{color:"blue"}}>男生必读</h3>
)} bookInfo={boyBook}></BookList>
<hr/>
<BookList render={()=>(
<h3 style={{color:"pink"}}>女生爱看</h3>
)} bookInfo={girlBook}></BookList>
</div>
);
}
export default App;
- components->BookList.jsx
import React, {Fragment} from 'react';
import PropTypes from "prop-types";
BookList.propTypes = {
// 接收的参数是数组,且不允许为空
bookInfo:PropTypes.array.isRequired,
// 接收的参数是一个函数,且不允许为空
render:PropTypes.func.isRequired
}
BookList.defaultProps = {
bookInfo:[]
}
function BookList(props) {
return (
<div>
{props.render()}
{
props.bookInfo.map(item=>(
<Fragment key={item.id}>
<img src={item.bookImg} alt=""/>
<p>{item.bookName}</p>
</Fragment>
))
}
</div>
);
}
export default BookList;
5-6-7- children
- src->App.jsx
import React, {Fragment} from 'react';
import BookList from "./components/BookList";
// 创建的组件在使用时,可以为其包裹内容。
// 包裹的内容如果是JSX,那么props.children得到的是React元素(虚拟DOM元素)
// 如果包裹的是多个并列关系的元素,那么props.children得到的是一个数组
// 在子组件中,可以通过props.children接收包裹的内容
function App(props) {
const boyBook = [
{
id:1,
bookName:"大明:我重生成了朱允炆",
bookImg:"https://static.zongheng.com/upload/s_image/cover/5a/84/5a84519422b2ab100f5bf318d8874a56.jpeg"
},
{
id:2,
bookName:"模拟修仙传",
bookImg:"https://static.zongheng.com/upload/s_image/cover/a3/5e/a35e28be78d8f6832f62386eb0abfe0d.jpeg"
}
];
const girlBook = [
{
id:11,
bookName:"王妃重生后忙着搞事业",
bookImg:"https://static.zongheng.com/upload/s_image/cover/b6/0d/b60d0b4f52d17b6e579de4c218883597.jpeg"
},
{
id:12,
bookName:"权宠娇娘",
bookImg:"https://static.zongheng.com/upload/s_image/cover/99/57/995725bacaef47e5a481d9b58555c261.png"
},
]
return (
<div>
<BookList bookInfo={boyBook}>
<h3 style={{color:"blue"}}>男生必读</h3>
</BookList>
<hr/>
<BookList bookInfo={girlBook}>
<h3 style={{color:"pink"}}>女生爱看</h3>
</BookList>
</div>
);
}
export default App;
- src->components->BookList.jsx
import React, {Fragment} from 'react';
import PropTypes from "prop-types";
BookList.propTypes = {
// 接收的参数是数组,且不允许为空
bookInfo:PropTypes.array.isRequired
}
BookList.defaultProps = {
bookInfo:[]
}
function BookList(props) {
// console.log(props.children);
return (
<div>
{
props.children
}
{
props.bookInfo.map(item=>(
<Fragment key={item.id}>
<img src={item.bookImg} alt=""/>
<p>{item.bookName}</p>
</Fragment>
))
}
</div>
);
}
export default BookList;
5-6-8- 使用图片
import React from 'react';
import vue from "./assets/img/vue.png";
import react from "./assets/img/react.png";
const vue2 = require("./assets/img/vue.png");
const react2 = require("./assets/img/react.png");
// 1- 如果图片来自于其它服务,直接写完整地址
// 2- 如果图片来自于本项目,那么可以通过import引入图片
// 3- 如果图片来自于本项目,那么也可以通过require
function App(props) {
console.log(require("./assets/img/vue.png"));
return (
<div>
{/*来自其它服务的图片*/}
<img src="https://static.zongheng.com/upload/s_image/cover/5a/84/5a84519422b2ab100f5bf318d8874a56.jpeg" alt=""/>
<img src={vue} alt=""/>
<img src={react} alt=""/>
<img src={require("./assets/img/vue.png")} alt=""/>
<img src={require("./assets/img/react.png")} alt=""/>
<img src={vue2} alt=""/>
<img src={react2} alt=""/>
</div>
);
}
export default App;
5-6-9- 使用样式
-
src->App.module.css
#appContainer .cl{ color:red; } -
src->App.jsx
import React from 'react';
import Child from "./components/Child";
import styles from "./App.module.css";
// import "./App.css"
// 可以使用样式的模块化
// 1- 样式文件的名字需要以.module.css结尾
// 2- 引入import styles from "./App.module.css";
// 3- 使用 className={styles.cl}
function App(props) {
return (
<div>
<p className={styles.cl}>app</p>
<Child></Child>
</div>
);
}
export default App;
5-6-10- 组件拆分
- App.jsx
import React from 'react';
import "./App.css";
import TodoHeader from "./components/TodoHeader";
import TodoMain from "./components/TodoMain";
import TodoFooter from "./components/TodoFooter";
function App(props) {
return (
<div className="todo-container">
<div className="todo-wrap">
{/*头部*/}
<TodoHeader/>
{/*列表*/}
<TodoMain/>
{/*底部*/}
<TodoFooter/>
</div>
</div>
);
}
export default App;
- src->components->TodoHeader.jsx
import React from 'react';
function TodoHeader(props) {
return (
<div className="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
);
}
export default TodoHeader;
- src->components->TodoMain.jsx
import React from 'react';
function TodoMain(props) {
return (
<ul className="todo-main">
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button className="btn btn-danger" style={{display:"none"}}>删除</button>
</li>
<li>
<label>
<input type="checkbox"/>
<span>yyyy</span>
</label>
<button className="btn btn-danger" style={{display:"none"}}>删除</button>
</li>
</ul>
);
}
export default TodoMain;
- src->components->TodoFooter.jsx
import React from 'react';
function TodoFooter(props) {
return (
<div className="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button className="btn btn-danger">清除已完成任务</button>
</div>
);
}
export default TodoFooter;
六、 React Hooks(重点)
6.1-Hooks介绍
官方介绍:
Hooks是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hooks 是一些特殊的函数,它可以让你“钩入” React 的特性。 hooks函数是以 use开头。
如何理解:
Hooks的单词意思为“钩子”。
React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。而React Hooks 就是我们所说的“钩子”。
React Hooks就是加强版的函数组件,降低了开发难度,我们可以完全不使用
class,就能写出一个全功能的组件。
6.2- 为什么要使用hooks
可以不用使用高阶组件依然可以进行功能复用了。
没有state,没有生命周期,维护起来比class组件简单N倍。
省去this的指向问题的学习。
避免开始写成functional组件,因为需要状态,又改成了class的成本问题。
……
6.3- 什么时候用 Hooks
官方: 如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class。现在你可以在现有的函数组件中使用 Hooks。
6.4- useState(常用)
useState是Hooks之一,可以让函数组件拥有类似与类组件中的state.
-
购物车加减操作
import React from 'react'; // 1- useState是React模块提供的一个方法 // 2- useState返回的是一个数组,接收的参数即是初始状态值。 // 3- 返回的数组有两个元素,第一个元素即是状态,第二个元素是修改状态的方法。 // 4- 当通过setXXX修改状态之后,函数会重新执行,注意:再次执行时,状态的值为上一次的结果(useState拥有记忆性) // 5- 修改状态的方法规范:以set开头,后续加上状态名,使用驼峰命名法 function App(props) { // 创建一个初始值为1的数据状态 // console.log(React.useState(1)); // 通过数组的解构赋值进行接收状态(num),以及修改状态的方法(setNum) const [num,setNum] = React.useState(1); return ( <div> <button onClick={()=>{ if(num>1){ setNum(num-1) } }}>-</button> {num} <button onClick={()=>{ setNum(num+1); }}>+</button> </div> ); } export default App; -
注意:如果状态是引用类型,那么需要复制,然后再进行操作
import React,{useState} from 'react'; function App(props) { // 创建一个初始值为100,名字为num的状态,修改状态的方法为setNum const [num,setNum] = useState(100); const [arr,setArr] = useState([1,2,3,4]) return ( <div> <button onClick={()=>{ const copyArr = [...arr]; copyArr.push(arr.length+1); setArr(copyArr);// 传入的参数要保证与修改的状态的引用地址不同。否则界面不会更新 }}>为数组增加数据</button> { arr.map((item,index)=>( <h3 key={index}>{item}</h3> )) } </div> ); } export default App;
6.5- useCallback
作用:记忆函数
import React, {useState,useCallback} from 'react';
// useCallback:可以将函数进行缓存处理。
// useCallback函数,接收的第一个参数是一个函数,第二个参数是数组
// 如果使用了useCallback,第二个参数建议声明,如果不声明相当于useCallback无用的方法。
// 如果省略第二个参数,不会有记忆。
// 如果只是写[],代表着该函数会进行记忆(缓存)
// 如果写的[num],代表着num发生变化,不会设置num缓存(保证num是最新的状态)。
function App(props) {
const [num,setNum] = useState(1);
const plus = useCallback(()=>{
setNum(num+1);
},[num])
const subtract = useCallback(()=>{
if(num>1){
setNum(num-1)
}
},[num])
return (
<div>
<button onClick={subtract}>-</button>
{num}
<button onClick={plus}>+</button>
</div>
);
}
export default App;
6.6- useMemo
作用:记忆结果。
import React, {useState,useCallback,useMemo} from 'react';
// useCallback记忆的是函数,useMemo记忆的结果(将接收函数执行以后的结果进行返回)
// useMemo第二个参数是数组。
// 如果将第二个参数设置为空数组,那么所得到值不会发生变化。
// 如果将第二个参数省略,那么不会产生记忆。
// 如果将第二个参数设置为[num],只有状态num发生变化,才会重新调用指定的函数,得到新的结果。
function App(props) {
const [num,setNum] = useState(1);
const plus = useCallback(()=>{
setNum(num+1);
},[num])
const subtract = useCallback(()=>{
if(num>1){
setNum(num-1)
}
},[num])
const result = useMemo(()=>{
return (
<div>
<button onClick={subtract}>-</button>
{num}
<button onClick={plus}>+</button>
</div>
)
},[num])
return result;
}
export default App;
6.7- useRef(常用)
import React, {useState,useRef} from 'react';
// 1- 引入useRef
// 2- 将useRef函数运行的结果赋值给常量。
// 3- 将生成的常量作为操作的React元素的ref属性值
// 4- 在使用时,可以通过常量名.current可以得到真实的DOM。
function App(props) {
const [num,setNum] = useState(1);
const btn = useRef();
const inputRef = useRef();
return (
<div>
<input ref={inputRef} type="text"/>
<button ref={btn} onClick={()=>{
// console.log(btn.current === document.querySelector("button"))
console.log(btn.current.innerText);
console.log(inputRef.current.value)
}}>{num}</button>
</div>
);
}
export default App;
6.8- useContext
useContext可以帮助我们解决跨组件传递数据,实现数据的共享。
-
src->context->index.js
import {createContext} from "react"; export default createContext(0); -
App.jsx
import React, {useState} from 'react'; import Child from "./components/Child"; import globalContext from "./context"; /*useContext的使用流程: * 1-要通过React.createContext方法生成一个数据(对象) * 1-1 createContext需要在src下创建一个目录context,在context中新建文件index.js进行执行。 * 1-2 createContext是一个函数,接收的数据是共享状态的初始值,返回的是一个对象 * 1-3 对象当中拥有Provider(提供者),Consumer(消费者) * 2-如果需要在Child组件中使用数据。 * 简单 : * import globalContext from "../context"; * <p>child:{result}</p> * 设置到数据的传递: * App.jsx: * const [num,setNum] = useState(100); * <globalContext.Provider> <Child userName={userName}/> </globalContext.Provider> 接收: import globalContext from "../context"; const {num,setNum} = useContext(globalContext); <p>child:{num}</p> * */ function App(props) { const [userName] = useState("zhangsan"); const [num,setNum] = useState(100); return ( <div> <globalContext.Provider value={{num,setNum}}> <Child userName={userName}/> </globalContext.Provider> </div> ); } export default App; -
components->Child.jsx
import React,{useContext} from 'react'; import globalContext from "../context"; import A from "./A"; import B from "./B"; function Child(props) { const {num,setNum} = useContext(globalContext); return ( <div> <p>child:{num}</p> <button onClick={()=>{ setNum(num+10) }}>child当中修改num</button> <hr/> <A userName={props.userName}/> <hr/> <B userName={props.userName}/> </div> ); } export default Child; -
components->A.jsx
import React, {useContext} from 'react'; import globalContext from "../context" function A(props) { const {num,setNum} = useContext(globalContext); return ( <div> <h3>A</h3> <p>{props.userName}</p> <p>num:{num}</p> <button onClick={()=>setNum(num+3)}>A修改num</button> </div> ); } export default A; -
components->B.jsx
import React, {useContext, useMemo} from 'react'; import globalContext from "../context"; // hook函数只允许写在函数组件或hook函数中 function useMy(){ return useContext(globalContext); } function B(props) { const {num,setNum} = useMy(); return ( <div> <h3>B</h3> <p>{props.userName}</p> <p>{num}</p> <button onClick={()=>setNum(num+9)}>B,修改num</button> </div> ); } export default B;
6.9- useReducer
useReducer是useState的一个替代方案。
useReducer也被称为状态管理器。通过useReducer可以对自身的状态进行集中式管理。
如果你的组件中的数据逻辑关系比较复杂。
import React,{useReducer} from 'react';
/*
reducer的使用流程:
1- 创建一个函数,该函数可以称为reducer函数,作用:可以对数据(数据状态)进行操作
2- 该函数可以接收两个参数,第一个参数是状态,第二个参数是荷载的内容.
3- 需要通过useReducer,可以获得状态以及修改状态的方法
4- useReducer接收两个参数:1-reducer函数 2- 初始状态
5- dispatch 执行时,需要传递一个对象.
* */
// prevState是要更改的状态,action的值为dispatch函数传递的数据(action对象)
// 返回的值是最新的状态.
// action对象需要设置type属性,通过type属性可以得知要对数据进行怎样的操作.
const reducer = function(prevState,action){
const state = {...prevState};
// 将状态更改为{num:100}
if(action.type === "jia"){
state.num+=action.payload;
}
if(action.type === "jian"){
if(state.num>1)
state.num-=1;
}
return state;
}
function App(props) {
// console.log(useReducer());// [undefined,f]
// console.log(useReducer(reducer,{
// num:1
// }));// [ {num: 1},f]
const [state,dispatch] = useReducer(reducer,{num:0});
return (
<div>
<button onClick={()=>{
dispatch({
type:"jian"
});
}}>-</button>
{state.num}
<button onClick={()=>{
dispatch({
type:"jia",
payload:2
});
}}>+</button>
</div>
);
}
export default App;
6.10- useEffect(常用)
- App.jsx
import React, {useEffect, useState} from 'react';
import Child from "./Child";
function App(props) {
const [num,setNum] = useState(0);
const [userName,setUserName] = useState("zhangsan");
const [isShow,setIsShow] = useState(true);
// 函数体的内容在挂载组件时(UNSAFE_componentWillMoount),更新状态时会执行(UNSAFE_componentWillUpdate)。
// console.log(document.querySelector("button"));
// 如果以下条件满足,说明组件已经挂载完毕了
// if(document.querySelector("button"))
// console.log(document.querySelector("button").innerText,num)
// 1- useEffect可以接收一个函数(相当于componentDidMount,componentDidUpdate)
// useEffect(()=>{
// console.log("useEffect",document.querySelector("button").innerText);
// })
// 2- useEffect第二个参数是一个数组,
// 2-1如果你省略该数组,当任何状态更新都会执行第一个参数(回调函数)
// 2-2如果第二个参数设置为一个空数组,那么回调函数只会在挂载完毕之后执行一次,更新阶段不会执行。(componentDidMount)
// 2-3如果数据内指定了状态,那么回调函数只会在指定的状态发生变化时和挂载结束时执行。componentDidMount componentDidUpdate
// useEffect(()=>{
// console.log("useEffect,num",document.querySelector("button").innerText);
// },[num])
//
// useEffect(()=>{
// console.log("useEffect,userName",document.querySelector("button").innerText);
// },[userName])
return (
<div>
<button onClick={()=>setNum(num+1)}>{num}</button>
<button onClick={()=>setUserName(userName+"!")}>{userName}</button>
<button onClick={()=>setIsShow(!isShow)}>显示与隐藏</button>
{
isShow && <Child></Child>
}
</div>
);
}
export default App;
- Child.jsx
import React, {useEffect} from 'react';
function Child(props) {
// 可以返回一个函数,该函数会在组件销毁前执行
useEffect(()=>{
console.log("componentDidMount");
return function(){
console.log("componentWillUnmount");
}
},[])
return (
<div style={{width:200,height:200,border:"1px solid green"}}></div>
);
}
export default Child;
6.12- 函数组件中使用axios
-
下载axios
cnpm install axios -
使用
import React, {useCallback, useEffect, useState} from 'react'; import axios from "axios"; function App(props) { const [items,setItems] = useState([]); // 如果不写第二个参数,那么useEffect的回调函数会在挂载完毕执行,会在状态更新之后执行。 // 第二个参数设置为[],可以避免重复发送请求 // useEffect(()=>{ // console.log("useEffect") // axios.get("https://api.github.com/search/repositories",{ // params:{ // q:"r", // sort:"stars" // } // }).then(({data})=>{ // setItems(data.items); // }).catch(err=>{ // alert("网络异常") // }) // },[]) const getGit = useCallback(async ()=>{ try{ // https://api.github.com/search/repositories?q=r&sort=stars const {data} = await axios.get("https://api.github.com/search/repositories",{ params:{ q:"r", sort:"stars" } }) setItems(data.items); }catch (err){ console.log(err); alert("网络异常",err); } },[]) // useEffect指定的回调函数不允许为async函数 useEffect(()=>{ // 1- useEffect回调函数中创建一个新的函数 // const getGit = async ()=>{ // try{ // // https://api.github.com/search/repositories?q=r&sort=stars // const {data} = await axios.get("https://api.github.com/search/repositories",{ // params:{ // q:"r", // sort:"stars" // } // }) // setItems(data.items); // }catch (err){ // console.log(err); // alert("网络异常",err); // } // // } // getGit(); // 2- 可以将async函数放置到外部 getGit(); },[]) return ( <div> { items.map(item=>( <p key={item.id}><a href={item.html_url}>{item.name}</a></p> )) } </div> ); } export default App;
6.11- useLayoutEffect
import React, {useEffect, useLayoutEffect, useState} from 'react';
function App(props) {
// 声明了状态num,初始值为0
const [num,setNum] = useState(0);
// useEffect:不会阻塞浏览器的渲染,会有一个短暂的闪烁,(抖动)
// useEffect(()=>{
// console.log("useEffect",num)
// const pre = Date.now();// 获取当前时间
// // 延迟0.5秒
// while(Date.now()-pre<500){}
// if(num === 0)
// setNum(Math.random());
// },[num])
// useLayoutEffect会阻塞浏览器的渲染。
useLayoutEffect(()=>{
console.log("useLayoutEffect",num)
const pre = Date.now();// 获取当前时间
// 延迟0.5秒
while(Date.now()-pre<500){}
if(num === 0)
setNum(Math.random());
},[num])
return (
<div>
<div onClick={()=>setNum(0)}>{num}</div>
</div>
);
}
export default App;