浏览器右上角 … ===> 更多工具 ===> 扩展程序 ==> 开启开发者模式 ==> 将调试插件包拖拽进来即可
React 需要引入两个包 React ReactDOM
需要创建一个根节点【舞台】,进行渲染
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>Documenttitle>
<script src="./lib/react.development.js">script>
<script src="./lib/react-dom.development.js">script>
head>
<body>
<div id="root">div>
<script>
// 创建一个根节点, 建立 react与dom元素的联系
// create 创建
// root 根
// createRoot : 创建根节点【搭建一个舞台】
const root = ReactDOM.createRoot(document.getElementById('root'))
// 使用 root.render(内容), 会将内容渲染到 id 是 root的节点中
root.render('hello React!')
script>
body>
html>
<!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="box"></div>
</body>
<script>
{
// 1. 可以在同一个页面有多个容器
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('root容器')
// const box = ReactDOM.createRoot(document.querySelector('.box'))
// box.render('box 容器')
}
{
// 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('首次渲染')
// root.render('二次渲染')
}
{
// 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root1.render('root 一')
// root2.render('root 二')
}
{
// 4. 不能将 html body 指定为 react容器
// html
// const root = ReactDOM.createRoot(document.documentElement);
// root.render('html')
//
// const root = ReactDOM.createRoot(document.body);
// root.render('body')
}
{
// 5. 支持链式调用
ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
}
</script>
</html>
<script>
/**
* react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
* 1. 页面是如何渲染的?
* 地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
* 1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
* 1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
* 1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
*
* 2. 什么是重排和重绘?
* css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
*
* 3. 传统DOM操作的缺陷!
* 频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
* this.style.height = 100px;
* this.style.left = '200px'
*
* 4. React、vue 性能更好,为什么?
* React 和 vue引入了虚拟dom 优化性能
*
* 5. 虚拟DOM是什么?
* 1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
* 2. 虚拟DOM 通过 render 可以转化为真实DOM
* 3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
* 4. 虚拟dom 在react中,我们又称之为时 react元素
* 5. 虚拟dom的创建有两种方法
* 1. 通过 React.createElement() 创建,[不会用它,了解]
* 2. 通过 jsx 创建
*
*/
</script>
createElement
创建 react元素语法:
React.createElement(标签名, 属性, 子元素1, 子元素2 .....)
const root = ReactDOM.createRoot(document.querySelector("#root"));
{
// 语法:React.createElement(标签名, 属性, 子元素1, 子元素2 .....)
// 1. 创建虚拟dom
const div = React.createElement('div', {id:'c1',school:'atguigu'},'shangguigu', 'yuonly','123123')
console.log('div: ',div)
// 通过render方法将虚拟dom转化为真实dom渲染到页面中
root.render(div)
// 真实dom
// const rootDiv = document.getElementById('root')
// console.log('rootDiv: ', rootDiv)
}
// 2. 如果没有标签属性,如何创建react元素.需要使用 null、undefined、{}、'' 对第二个参数进行占位
const span = React.createElement('span',null,'我是 span')
// const span = React.createElement('span','','我是 span')
// const span = React.createElement('span',{},'我是 span')
// const span = React.createElement('span',undefined,'我是 span')
// const span = React.createElement('span',123123,'我是 span') // 不报错,但不推荐
console.log(span)
root.render(span)
className
样式类 class 与 js 的关键字class 同名,所以,使用
className
// 特殊属性 className
const root = ReactDOM.createRoot(document.querySelector('#root'))
// 创建span标签 类名是 c1
const span = React.createElement('span', { className: 'c1' }, '文字是红色')
console.log(span);
root.render(span)
// 为什么class属性,要使用className
// 因为class 是js的关键字,用来定义 类的
const p1 = document.getElementById('p1')
// 真实DOM元素,也是用className 来定义 class的
// react元素的属性和真实dom的属性,是一一对应的
console.dir(p1)
/**
*
* 当标签套标签时,就需要进行 react元素的嵌套
*/
const root = ReactDOM.createRoot(document.querySelector("#root"));
const span = React.createElement('span',{className:'red'},'我是span')
const div = React.createElement('div', {className:'c1'}, span , React.createElement('p',{school:'atuigu'},'我是p标签'))
root.render(div)
jsx
jsx = js + xml
作用:可以快速创建react元素【虚拟dom】
xml: 是一种数据结构,是json格式的数据出现之前,使用的通信数据结构
<user> <name>atuiguname> <age>10age> user> let user = { "name":"atuigu", "age":10 }
需要两个条件
- 引入 babel ,因为浏览器本身不认识jsx
- script标签加上type=“text/babel”
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<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"));
// const div = React.createElement('div',null,'div元素')
// root.render(div)
// jsx 创建react元素
// 浏览器本身不认jsx语法,需要进行语法转化,使用babel进行转化
let div = <div>div元素</div>
root.render(div)
script>
html>
jsx
语法书写非常像html标签,但本质是 react元素。最终在底层会转化为React.createElement
的语法。构建复杂结构页面时,使用 () 括起来,可以进行换行
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="./lib/react.development.js">script>
<script src="./lib/react-dom.development.js">script>
<script src="./lib/babel.min.js">script>
<style>
.c1{
color:red;
}
style>
head>
<body>
<div id="root">div>
body>
<script type="text/babel">
const root = ReactDOM.createRoot(document.querySelector("#root"));
// const div = React.createElement('div',null,'div元素')
// root.render(div)
// jsx 创建react元素
// 浏览器本身不认jsx语法,需要进行语法转化,使用babel进行转化
// let div = div元素
// root.render(div)
// 创建复杂结构,使用 ()括起来,可以进行换行
let div = (
<div className="c1">
<h1>jsx创建react元素</h1>
<span>我是span</span>
<p>我是p标签</p>
<div>
<span>jsdklfj</span>
</div>
</div>
)
root.render(div)
script>
html>
jsx
标签注意事项
jsx
标签只能写两种:
- html标签: 全部用小写
- 组件标签:首字母大写
const root = ReactDOM.createRoot(document.querySelector("#root"));
/**
* 注意事项:
* 1. jsx 语法标签名如果是html元素标签,要用小写
* 2. jsx 只能写html标签,写别的不报错。但是 react元素最终会render转化为真实dom。
* 写别的能转化也能渲染,但是浏览器不认识,没有意义
* 3. jsx除了写浏览器认识的标签,首字母大写的标签,会当做组件来处理
*
* === > Student 是一个组件
* === > Header 组件
*
*/
root.render((
<div>
<username>yuonly</username>
<age>age</age>
</div>
))
语法: { js 表达式 }
<!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">
/**
* js代码由两部分组成: 1. js语句 2. js表达式
* js语句: 赋值语句、if。。。else 条件判断语句、for 循环语句、switch 语句
* 作用:控制代码的运行走向。语句是没有值
*
* js表达式:有值的js代码
* 1. 三元表达式
* 2. 逻辑表达式: 5 && 8
* 3. 变量、常量
* 4. 函数返回值: fn()
*
* jsx 中的插值表达式:
* 作用:将表达式的值,渲染到页面
* 语法: {js表达式}
* 数据类型: usonb you so niu b
* u:undefined
* s:string、symbol
* o:object [object、function、数组]
* n: null、number
* b: boolean: 使用插值语法,什么都不渲染
* 1.
*
*/
const root = ReactDOM.createRoot(document.querySelector("#root"));
const num = 2;
const obj = { username: 'atuigu', age: '10' }
const fn = function () {
console.log(123)
//return {name:'atuigu'} // 报错
return 123; //渲染123
return undefined;// 什么都不渲染
}
const flag = 1;
root.render((
<div>
<h3>数据类型:</h3>
<p>数字:{1} - {num}</p>
<p>字符串: {'我是字符串'}</p>
<p>布尔值-true: {true}</p> {/*布尔值true:使用插值语法,什么都不渲染*/}
<p>布尔值-false: {false}</p> {/*布尔值false:使用插值语法,什么都不渲染*/}
<p>null: {null}</p> {/*null:使用插值语法,什么都不渲染*/}
<p>undefined: {undefined}</p>{/*undefined:使用插值语法,什么都不渲染*/}
{/*对象: {{username:'atuigu',age:'10'}}
对象:直接报错,对象不是一个合法的react子元素*/}
<p>数组: {[1, 2, 3, 4]}</p>{/*将数组中的每一个元素遍历出来,挨个进行渲染*/}
{/*以下代码会报错,因为会把数组中每一个元素拿出来渲染,发现对对象,渲染不了*/}
{/*数组中的元素是对象呢?{[{id:1,name:'atguigu'},{id:2,name:'yuonly'}]}
*/}
{/*函数: {fn}
函数的定义也无法正常渲染 */}
<p>函数的返回值: {fn()}</p>
<p>三元表达式: {flag === 1 ? '真' : '假'}</p>
<p>逻辑&& 运算: {5 && 8}</p>
<p>逻辑|| 运算: {5 || 8}</p>
</div>
))
</script>
</html>
单分支: 如果 a成立,那么…
- sex === ‘男’ && ‘boy’
- sex !== ‘男’ || ‘boy’
- if(sex===‘男’) ‘boy’
双分支的: 如果a成立,xxx,否则,
- xxx if(){}else{}
- 三元表达式
多条件的: 封装成一个函数处理
Title
- 表达式外部没有 “”
- 自定义属性也可以赋值
- 可以使用… 扩展运算进行批量赋值
const root = ReactDOM.createRoot(document.querySelector("#root"));
const width = 300
const height = 100
const styles = {
width:500,
height:150,
school:'atguigu'
}
root.render((
123123
{/*一:属性可以用插值表达式赋值:表达式外部不要有""号 */}
id
name
操作
{/*二:自定义属性也可以用插值表达式赋值 */}
id
name
操作
{/*三:使用扩展运算,批量进行属性赋值 */}
id
name
操作
))
行内样式使用 style属性,属性值必须是一个对象
- style的值必须是一个对象:第一个{}表示的是插值表达式的边界, 第二个{}表示对象的边界
- 如果样式的单位是px,那么px可以省略,值直接写成数字即可
- 如果是复合属性,那么要使用小驼峰命名法
const styles = {
width: '300px',
height: '100px',
background: 'red'
}
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render((
我是测试行内样式的div
{/* style的值必须是一个对象:第一个{}表示的是插值表达式的边界, 第二个{}表示对象的边界 */}
我是测试行内样式的div
{/* 如果样式的单位是px,那么px可以省略,值直接写成数字即可 */}
我是测试行内样式的div
{/* 如果是复合属性,那么要使用小驼峰命名法 */}
我是测试行内样式的div
))
className
通过类名定义样式必须使用
className
指定类名,不能使用class
className
:可以直接赋值字符串className
: 多个样式类名中间用空格隔开,可以同时生效- 如果样式是一个数组,使用 join(’ '),进行赋值渲染
const root = ReactDOM.createRoot(document.querySelector("#root"));
const classStr = 'c1 bg1'
const classArr = ['c1', 'bg1', 'f1']
root.render((
c1 class 测试
bg1 class 测试
f1 class 测试
c1 bg1 同时,字符串写法
c1 bg1 同时,字符串写法
{/*
插值语法渲染数组,如果是渲染内容,那么挨个遍历渲染
如果数组是渲染className , 那数组中的每一个元素中间用 , 隔开了
*/}
c1 bg1 f1 样式同时出现 数组写法
c1 bg1 f1 样式同时出现 数组写法,直接写不好使,中间有,
c1 bg1 f1 样式同时出现 数组写法
{classArr}
))
利用react渲染数组,是将数组中的每一项,都拿出来自动遍历的特性,将普通的数组数据,通过map方法,映射成react元素的数组。进而实现列表数据的渲染
- key的问题:
- 作用:用于react进行diff算法时,就节点进行比较,可以提升渲染效率,实现最小量更新的目的【后面会详细讲】
- key必须不能重复
- key的取值问题:
- 使用数据的唯一标识作为key值,一般是 id
- 也可以使用 index 索引作为key【特定情况可用,不推荐】
- key 最终不会被渲染到真实dom上,仅用于diff算法
const root = ReactDOM.createRoot(document.querySelector("#root"));
// {
// // 1. {} 可以将数组中的每一个元素,都遍历出来直接渲染
// const arr = [1, 2, 3, 4]
// root.render((
// {arr}
// ))
// }
{
// 2. 插值表达式渲染的数组,数组中的元素可以是react元素
// const arr = [1
, 2
, 3
]
// root.render((
// {arr}
// ))
}
{
// 3. 将普通数组,转化为react元素的数组,然后渲染
// 3-1. for循环进行转化
// let arr = [1,2,3,4]
// let elementArr = []
// for(let i = 0;i{arr[i]}
// }
// root.render((
// {elementArr}
// ))
// 3-2. forEach转化
// let arr = [1,2,3,4]
// arr.forEach((item,index)=>{
// arr[index] = {item}
// })
// root.render((
// {arr}
// ))
// 3-3. map转化 [推荐]
// map: 将原数组中的每一个元素,进行映射,映射出一个新数组,新数组的长度和原数组一致
let arr = [1, 2, 3, 4]
// let newArr = arr.map(item=>{
// return item * 2
// })
// console.log(arr,newArr)
// let newArr = arr.map(item => {
// return {item}
// })
// console.log(newArr)
// root.render((
// {newArr}
// ))
// 下面代码与上面 7 行等价
// root.render((
// {arr.map(item => {item}
)}
// ))
// 4. 关于控制台报的 key的错误,解决方案
// 当做列表渲染的时候,由于diff 算法的原因,需要给每一个遍历的元素标签加上唯一的 key属性值
// key属性值不能重复.
// 所以我们一般使用数据的 id [唯一标识作为key值]
// key属性仅用作diff算法,渲染完成后,不会出现在真实dom标签上
// key 可以使用 索引作为key值,但是不推荐。
// root.render((
// {arr.map(item => {item}
)}
// ))
// 5. 使用map渲染元素是对象的数组
const users = [
{
id: 1,
username: 'atguigu',
pwd: 'skdjflskfds'
},
{
id: 2,
username: 'yuonly',
pwd: '243124'
},
{
id: 3,
username: 'houhou',
pwd: '12312321'
}
]
root.render((
{/*
{users.map(u => {
return (
-
id:{u.id}
username:{u.username}
pwd:{u.pwd}
)
})}
以上代码还可以省略 {} 和return 见下面
*/}
{users.map(u => (
-
id:{u.id}
username:{u.username}
pwd:{u.pwd}
))}
))
}
let aBtn = document.querySelector('a')
aBtn.onclick = function(e){
console.log('a')
// 阻止默认行为
// 方式一:
// e.preventDefault();
// 方式二:
return false;
}
function btn1Fn(e, that){
console.log('btn',e)
// 此处this指向window,说明函数的调用者是 window
console.log(this)
// 要想获取指向当前按钮的实例,需要通过参数进行接收
console.log('that: ',that)
}
let input1 = document.querySelector('[name="username"]')
// 绑定onchange事件: 原生onchange事件,是文本框失去焦点时触发
// onchange 的事件对象类型就是 Event
input1.onchange = function(e){
console.log('onchange e: ',e)
console.log('input value : ',e.target.value)
}
let input2 = document.querySelector('[name=pwd]')
// 原生的oniput事件:是输入内容有变化就触发. 事件类型是 InputEvent
input2.oninput = function(e){
console.log('oninput e: ',e)
console.log('pwd value: ', e.target.value)
}
- 原生的绑定事件: on+事件名[小写的] ==> onclick onchange oninput onmousemove
- jsx中的事件绑定: on + 事件名[首字母大写] => onClick onChange onInput onMousemove
onClick={函数的定义}
jsx中的事件对象:是经过react包装处理后的事件对象,原生的事件对象存储在了 navtiveEvent属性上
jsx事件对象更好用,因为react已经帮我们做完了兼容性的处理
jsx中的事件回调函数,是window调用的,但react中是严格模式,所以,this指向undefined
jsx中的 onChange事件,就是原生dom事件中的 oninput事件
阻止默认行为: `e.preventDefault()`
Title
需求:实现评论列表功能
- 如果有评论数据,就展示列表结构 li( 列表渲染 )要包含a标签
- name 表示评论人,渲染 h3
- content 表示评论内容,渲染 p
- 如果没有评论数据,就展示一个 h1 标签,内容为: 暂无评论!
- 用户名的字体25px, 内容的字体20px
- 点击内容区域提示它发表的时间
const list = [ { id: 1, name: 'jack', content: 'rose, you jump i jump', time: '03:21' }, { id: 2, name: 'rose', content: 'jack, you see you, one day day', time: '03:22' }, { id: 3, name: 'tom', content: 'jack,。。。。。', time: '03:23' } ]
const list = [
{ id: 1, name: 'jack', content: 'rose, you jump i jump', time: '03:21' },
{ id: 2, name: 'rose', content: 'jack, you see you, one day day', time: '03:22' },
{ id: 3, name: 'tom', content: 'jack,。。。。。', time: '03:23' }
]
const root = ReactDOM.createRoot(document.querySelector("#root"));
function fn(item){
alert(item.time)
}
let vdom = (
list.length === 0 ? 暂无评论
: (
{list.map(item=>(
-
e.preventDefault()}>
{item.name}
fn(item)}>{item.content}
))}
)
)
root.render((
{vdom}
))
多行注释 {/* */}
单行注释
{ // }
属性注释
/* */
const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render((
{/* 1. 多行注释 【推荐】*/}
{/*
中间都是注释的内容
*/}
{/* 2. 单行注释 [一般不用]*/}
{
// 单行注释代码
// 仅我可见as
}
{/*3. 属性注释*/}
))
React.Fragment
react元素必须有唯一的根节点,但会导致多一层html标签结构,如果你希望直接渲染子几点到页面中,就可以使用文档碎片。将多个子节点放入文档碎片中。让文档碎片充当唯一的根节点。在渲染成真实dom时,文档碎片不被渲染,只渲染它内部的内容
const root = ReactDOM.createRoot(document.querySelector("#root"));
const { Fragment } = React
const vdom = (
// 写法一
//
//
// 我是div1
//
//
// 我是div2
//
//
// 写法二:
//
//
// 我是div1
//
//
// 我是div2
//
//
// 写法三:
<>
我是div1
我是div2
>
)
root.render(vdom)
create-react-app
作用:快速构建一个工程化的react开发环境
# 方式一: 使用npm安装
npm i create-react-app -g # 就会多一个命令 create-react-app 的命令
# 方式二: 使用yarn安装 [了解]
yarn add create-react-app global
# npm [课堂使用]
create-react-app 项目名 # 项目名不要叫 react, 不要有中文
# yarn [了解]
yarn create react-app 项目名
# npm
cd 项目名 # 进入项目的根目录
npm start
# yarn
cd 项目名
yarn start
# 初始化项目
yarn init
# 安装
yarn add 包名
yarn add 包名 global
# 删除包
yarn remove 包名
yarn reomve global 包名
# 运行
yarn start
# 通过git clone 项目后,是没有 node_modules目录的,安装依赖包
yarn
yarn 项目的版本锁文件: yarn.lock
npm 项目的版本锁文件: package-lock.json
react_study
|- node_modules 项目根目录
|- public 网站静态资源目录
| |- favicon.ico 站点图标
| |- index.html 网站入口html文件
| |- logo192.png manifest.json要用的移动端图标
| |- logo512.png manifest.json要用的移动端图标
| |- manifest.json 移动端的配置文件
| |- robots.txt 可以爬取网站哪些内容
|-src 源码开发目录
| |- App.css App根组件样式文件
| |- App.js App根组件
| |- App.test.js 测试文件
| |- index.css 全局公共样式文件
| |- index.js js的入口文件
| |- logo.svg 项目启动后旋转的react图标
| |- reportWebVitals.js google的一个报告文件
| |- setupTests.js 测试启动文件
|- .gitignore git忽略文件
|- package-lock.json npm依赖包锁文件
|- package.json npm的配置文件
|- README.md 项目说明文件
public目录只保留:favicon.ico、index.html
src目录: App.js[根组件]、index.js 【js入口文件】
DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<title>React Apptitle>
head>
<body>
<div id="root">div>
body>
html>
export default function App(){
return (
App12312312
)
}
import React from 'react'
import ReactDOM from 'react-dom/client'
// 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App/>)
// react 17 语法
//import React from 'react'
// 差别一:ReactDOM 引入的包名不一样
//import ReactDOM from 'react-dom'
//import App from './App'
// 差别二:无序创建root 使用ReactDOM直接render
//ReactDOM.render( , document.getElementById('root'))
// react 18 版本
import React from 'react'
import ReactDOM from 'react-dom/client'
// // 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App/>)
什么是组件?
组成页面整体的部件,称为组件。
一个页面:由若干的组件组成
具有独立html结构、css样式、js逻辑、一些静态资源
react中的组件有两种:
- 类组件:react16.4版本以前,开发主要使用类组件
- 函数组件:react16.4版本以后,主要使用函数组件
组件开发优势:
- 方便复用
- 多人协作开发
一、类组件定义:
类组件使用class进行声明定义
类组件要继承
React.Component
类类组件的类名就是组件名
类组件类名首字母必须大写
类组件,必须有render方法
render方法执行后,必须要有返回值,返回值是什么,调用组件后渲染的内容就是什么。
99.999% 返回值都是一个react元素
二、类组件的调用
组件使用jsx语法进行调用
1-1.单标签:<组件名 />
1-2.对标签:<组件名>
类组件被调用,是如何执行的
当遇到 时,首先会实例化 该组件 new App()
使用实例化出来的对象,调用 render方法
将render方法 return 的内容,替换掉 调用标签
import React from 'react'
import ReactDOM from 'react-dom/client'
// // 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App/>)
import React,{Component} from 'react'
export default class App extends Component{
render(){
console.log(this)
return (
App - class
)
}
}
一、函数组件的定义
- 函数组件就是一个函数
- 函数名就是组件名
- 函数名首字母必须大写【因为组件在调用的时候,是使用jsx语法调用的】
- 函数的返回值,就是组件渲染的结果
二、函数组件的调用
- <组件名/> 调用该组件,就是执行组件函数
- 函数的返回值,就是渲染的结果,替换掉调用标签
import React from 'react'
import ReactDOM from 'react-dom/client'
// // 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<App/>)
function App(){
return (
App - function
)
}
export default App
- 调用后执行的方式不同
- 类组件:实例化类之后,执行render函数
- 函数组件:直接调用执行函数
- this指向不同
- 类组件有this: this指向当前组件的实例对象
- 函数组件this: 没有this,this是undefined
- 是否有自身的状态数据:
- 类组件: 有自身的状态数据 state
- 函数组件:本身没有状态数据 ,后期可以通过 hook函数具有状态数据
- 是否有生命周期:
- 类组件:有生命周期
- 函数组件:本身没有生命周期,后期可以通过hook函数模拟生命周期
创建状态数据
- 创建类的属性 state: 在构造函数中创建
constructor(){ // 因为App继承了 Component,所以首先要调用父类的构造函数 super() // 定义自身的属性 // state属性是特殊的,状态数据的属性,值一般是一个对象,对象中的属性就是状态数据 this.state = { count:10, msg:'atguigu' } // 构造函数,都是类的实例调用的,所以此处的this永远指向组件的实例对象 }
读取:
- 直接读取:
{this.state.count}
- 解构之后读取:
let {count,msg} = this.state {count} {msg}
改变状态
前提:事件的回调函数中的this必须要指向 当前组件的实例对象[ 箭头函数 ]
直接赋值:
this.state.count += 1
问题:可以改变状态count的值,但是不能触发render方法的重新调用,页面看不到最新的结果
通过setState函数设置:
this.setState({ count:this.state.count + 1 })
改变状态count ,并且会触发render方法的重新调用,页面可以看到改变后的数据
class App extends Component{
// 在构造函数中,[类的状态数据] 就是定义该类的 state属性
constructor(){
// 因为App继承了 Component,所以首先要调用父类的构造函数
super()
// 定义自身的属性
// state属性是特殊的,状态数据的属性,值一般是一个对象,对象中的属性就是状态数据
this.state = {
count:10,
msg:'atguigu'
}
// 构造函数,都是类的实例调用的,所以此处的this永远指向组件的实例对象
}
render(){
console.log('render执行了')
// 可以解构之后在读取
let {count, msg} = this.state
return (
class App
count: {this.state.count}
msg: {msg}
)
}
}
- constructor 构造函数中定义
- state直接赋值定义 【相当于也是在constructor中执行的,只不过执行时机比constructor要早】
- 混合定义
class App extends Component{
// 1. 方式一:constructor中定义
// constructor(){
// super()
// this.state = {
// count:1
// }
// }
// 2. 方式二: 直接赋值state定义 [推荐]
// 直接赋值相当于是在 constructor中调用的。
state = {
count:1
}
// 3. 方式三:混合定义[了解]
// constructor(){
// super()
// // 后执行,可以给state增加属性
// // 直接赋值,可以改变状态数据,并且不会触发render中心执行
// this.state.msg = 'atguigu'
// }
// state = {
// count:1
// }
render(){
return (
count:{this.state.count}
{/* msg:{this.state.msg}
*/}
)
}
}
组件的嵌套:在一个组件中调用另一个组件,就形成了组件的嵌套
组件嵌套会产生父子组件的概念:
- 外部组件是内部组件的父组件
- 内部组件是外部组件的子组件
import React, { Component } from 'react'
// 导入Child组件
import Child from './components/Child'
/**
* 在一个组件中,调用另一组件,组件就形成了嵌套关系
* 外部组件称为内部组件的父组件
* 内部组件是外部组件的子组件
*/
export default class App extends Component {
render() {
return (
App组件
)
}
}
import React, { Component } from 'react'
export default class Child extends Component {
render() {
return (
Child
)
}
}
props通信:
- 数据的传递:在父组件中通过给子组件绑定属性的方式传递数据
- 数据接收:在子组件中通过this.props 特殊属性,接收到父组件传递过来的数据
import Child from './components/Child'
export default class App extends Component {
render() {
return (
App组件
{/*通过属性传递数据给子组件*/}
)
}
}
import React, { Component } from 'react'
export default class Child extends Component {
render() {
console.log('child: ',this.props)
let {username, age} = this.props
return (
Child
props username: {this.props.username} - {username}
props age: {this.props.age}- {age}
)
}
}
父组件向子组件传递状态数据
- 传递方式:调用子组件的标签,通过属性传递
- 逐个属性单独赋值传递
- 通过…扩展运算批量传递
- 接收方式: 子组件中通过 this.props特殊属性接收
import React, { Component } from 'react'
// 导入Child组件
import Child from './components/Child'
/**
* 传递父组件的状态数据给子组件
*/
export default class App extends Component {
// 定义状态
state = {
num:1000,
message:'hello, 各位大哥,别迟到了!'
}
render() {
// 解构状态数据
let {num, message} = this.state
return (
App组件
子组件中接收数据,取决于属性名,而不是变量名
通过扩展运算... 批量传递状态数据
)
}
}
import React, { Component } from 'react'
export default class Child extends Component {
render() {
console.log('child: ',this.props)
let {username, age, num, message} = this.props
return (
Child
props username: {this.props.username} - {username}
props age: {this.props.age}- {age}
App state count: {num}
App state msg: {message}
)
}
}
函数组件接收外部数据,是通过函数参数接收。一般直接在参数位置进行解构
/**
* 函数组件如何接收外部数据?
* @ props: 函数组件第一个参数,就是外部数据
参数直接结构:推荐写法
* @returns
*/
export default function Child({num, message, age}){
return (
Child 函数组件
app num: {num}
app message: {message}
app age: {age}
)
}
// 如何获取和使用外部数据【不推荐】
// export default function Child(props){
// console.log('props: ',props)
// let {num, message, age} = props
// return (
//
// Child 函数组件
// app num: {props.num}
// app message: {props.message}
//
// app num: {num}
// app age: {age}
// app message: {message}
//
// )
// }
父组件通过props传递一个方法给子组件
步骤:
在父组件中定义一个方法
将该方法通过标签属性的方式,传递给子组件
改变this指向之后,在传递,确保函数内部的this指向父组件的实例对象
因为,接收到数据之后,我们一般会改变父组件的状态
子组件通过props接收
子组件中调用该方法,并将要传递的数据以参数的形式给父组件
import Child from './components/Child'
export default class App extends Component {
// 定义状态
state = {
num:1000,
message:'hello, 各位大哥,别迟到了!'
}
// 1. 在父组件中定义一个方法
getSonData(a){
console.log('App a:',a)
// getSonData是普通函数,this指向取决于调用者是谁
console.log('getSonData this', this)
// 我想改变状态数据,就必须让this指向当前组件的实例对象
// 怎么改变 getSonData函数中的this指向?
this.setState({
num: this.state.num + a
})
}
render() {
let {num,message} = this.state
return (
App父组件
{/* 2. 通过标签属性,传递方法 */}
)
}
}
/**
* 函数组件如何接收外部数据?
* @ props: 函数组件第一个参数,就是外部数据
* @returns
*/
// 3. 在子组件中通过props接收从父组件传递过来的 getSonData 方法
export default function Child({ num, message, age, getSonData }) {
return (
Child 函数组件
app num: {num}
app message: {message}
app age: {age}
)
}
import {Component} from 'react'
export default class Child extends Component {
render(){
let {num, message, age, getSonData} = this.props
return (
Child 类组件
app num: {num}
app message: {message}
app age: {age}
)
}
}
外部传入的数据props是只读的,在子组件中不可以直接赋值修改
如果想改,需要到数据的来源,父组件中修改。需要用到 子传父的知识点
import React, { Component } from 'react'
import Child from './components/Child'
export default class App extends Component {
state = {
count:1
}
// 1. 定义方法
setCount(num){
this.setState({
count:this.state.count + num
})
}
render() {
let {count} = this.state
return (
App
{/* 2. 改变this指向后,将方法给子组件 */}
)
}
}
import React, { Component } from 'react'
export default class Child extends Component {
render() {
let {setCount} = this.props
return (
Child
app count props: {this.props.count}
)
}
}
props.children
props.children
: 可以获取到对标签调用组件时,对标签中间的内容这也是单标签和对标签调用组件的差别
- <组件名 />
- <组件名>内容会被
props.children
接收到
import React, { Component } from 'react'
import Button from './components/Button'
export default class App extends Component {
render() {
return (
App
)
}
}
import React from 'react'
export default function Button(props) {
console.log('props: ',props)
return (
)
}
可以使用prop-types 包,来对外部传入的数据类型和默认值进行限定
- 类组件:
- static propTypes: 限定 类型和是否必填
- static defaultProps: 限定默认值
- 函数组件:
- 函数名.propTypes: 限定 类型和是否必填
- 函数名.defaultProps: 限定默认值
import React, { Component } from 'react'
import PropTypes from 'prop-types'
export default class PropsClass extends Component {
// 定义校验规则
// isRequired 必填
static propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number
}
// 设置默认值
static defaultProps = {
age:10000
}
render() {
return (
类组件props测试
name: {this.props.name}
age: {this.props.age}
)
}
}
import React from 'react'
import PropTypes from 'prop-types'
export default function PropsFun({name,age}) {
return (
函数组件props限定
name: {name}
age: {age}
)
}
//校验规则
PropsFun.propTypes = {
name:PropTypes.string.isRequired,
age:PropTypes.number
}
// 默认值
PropsFun.defaultProps = {
age:900
}
事件回调的函数,都是window调用的
有三种方案,使得事件回调的this指向当前组件的实例
- 包裹箭头函数【推荐】
- 可以传参
- 方法是定义在原型上的
- bind【推荐】
- 可以传参
- 方法是定义在原型上的
- 父子组件通信时,传递给子组件的方法推荐用bind
- 直接定义成箭头函数【不推荐】
import React, { Component } from 'react'
export default class App extends Component {
state = {
count:1
}
click1(num){
// 普通方法,this只跟调用者有关。
// click1 作为单击事件回调的函数,调用者是window,因为是严格模式,所以是undefined
console.log('click1: ',this)
this.setState({
count: this.state.count + num
})
}
click2(num){
console.log('click2 this: ', this)
this.setState({
count:this.state.count + num
})
}
click3(num){
console.log('click3 this: ', this)
this.setState({
count:this.state.count + num
})
}
click4 = (num)=>{
console.log('click4 this: ', this)
this.setState({
count:this.state.count + num
})
}
render() {
// render中的this永远指向当前组件的实例对象
return (
App
count: {this.state.count}
{/*
包裹箭头函数:
优点: 1,函数是定义在类的原型对象上的。共用一个
2. 可以传递参数
*/}
{/*
bind会返回一个新函数,新函数的this已经被改变了,用的也是render中的this
优点:1. 函数是定义在类的原型对象上的。共用一个
2. 可以传参
3. 返回一个新函数,this指向已经改变了的。传递给子组件时,非常方便
*/}
{/*
click4 中的this 用的是constructor中的this.
缺点:1. 不能传参
2. 箭头函数不是定义在原型对象上的,每一个实例都有一个自己的。性能也不好
*/}
)
}
}
setState
深入理解this.setState 用来改变状态数据。是异步的改变
用法:
参数是一个对象:this.setState({count:1})
参数是一个回调函数:
- 回调函数:是异步的
- 回调函数的参数,是最新的改变后的状态数据
- 回调函数的返回值,是最新的状态数据
this.setState(state=>{ return { count:state.count + 1 } })
import React, { Component } from 'react'
export default class App extends Component {
state = {
count: 1,
msg:'atguigu'
}
render() {
console.log('render 执行了')
return (
count: {this.state.count}
)
}
}
- 成熟的第三方库:
- 文件放置位置:
项目目录/public/css/xxxx.css
- 引入的方式:
项目目录/public/index.html
head中 通过link标签引入- 自定义的全局样式:
- 文件放置位置:
src/App.css
- 引入位置:
src/App.jsx
中通过import 语法引入- 组件独有样式:
- 文件放置位置:
组件目录/css/xxx.css
- 引入位置:
组件中使用 import 语法引入
css in js
将css文件样式转化为js模块来使用,好处是避免不同的组件的相同的样式名的冲突
确保各个组件样式的独立性
实现模块化步骤:
- 命名:css文件必须以 xxx.module.css 命名
- 引入: import styles from ‘./xxxx.module.css’
- 使用:styles.类名
直接将图片路径填到 img src属性即可
- require
- 注意:静态资源的目录,必须放在src目录下面。
- import
import React, { Component } from 'react'
import img from './assets/images/2.webp'
export default class App extends Component {
render() {
return (
图片
网络图片
本地图片
)
}
}
- 完成组件拆分
- 完成静态布局
- 首屏数据渲染
- 完成交互
import React, { Component } from 'react'
import './css/index.css'
export default class Swipper extends Component {
state = {
index:4
}
render() {
let {index} = this.state
return (
{
index--;
if(index <=0 ) index = 4;
this.setState({
index
})
}}> <
{
index++;
if(index >=5 ) index = 1
this.setState({
index
})
}} className="right"> >
)
}
}
css
.wrapper{
position: relative;
width:600px;
height:240px;
border:1px solid red;
}
.wrapper span{
position: absolute;
top:90px;
height:60px;
width:24px;
background-color:forestgreen;
text-align: center;
line-height: 60px;
cursor: pointer;
user-select: none;
color:white;
font-weight: 700;
}
.wrapper .right {
right: 0;
}
.wrapper img{
position:absolute;
top:0;
left:0;
height:240px;
width:600px;
}
import React, { Component } from 'react'
import './index.css'
export default class Tab extends Component {
state = {
index:1
}
render() {
let {index} = this.state
return (
Tab
北京
上海
深圳
)
}
}
.box{
width:500px;
height:300px;
border:1px solid red;
}
.box .show{
width:100%;
height:240px;
border:1px solid blue;
display: none;
}
.box button{
margin-right: 10px;
}
.box .active{
background-color: aqua;
}
执行顺序:constructor=> render => componentDidMount
constructor、componentDidMount 只执行一次,挂载后不在重复执行
render: 挂载阶段执行,更新也会执行
componentDidMount 一般会做如下操作
- 开启定时器
- 发送ajax请求
- 订阅消息
- 添加事件监听
存在父子组件嵌套时
先执行父组件的 constructor、render
执行子组件的 constructor、render、componentDidMount
执行父组件的 componentDidMount
- 执行顺序: render ==> componentDidUpdate
- 什么方式可以触发更新?
- new props: props数据发生变化时
- setState: 重新设置状态数据时
- forceUpdate: 强制更新
componentDidUpdate
【不常用】
- 发送ajax请求
- 将数据做本地化存储
- 对于组件来说,父组件更新了[render], 子组件无条件也会 render
shouldComponentUpdate
【了解】
问题:类组件当外部数据重新设置,即便没有变化也会重新渲染,当状态数据重新设置,即便没有变化,也会重新渲染
作用:可以控制组件的render是否执行。可以实现:当组件中的props和state数据都没有变化的时候,不重新渲染
should 应该 component 组件 update更新: 组件应该更新么?
- nextProps: 即将要更新到的props值
- nextState:即将要更新到的 state值
- return boolean: true 组件更新 、false组件不更新
shouldComponentUpdate(nextProps, nextState){
// console.log('this.props: ',this.props)
// console.log('nextProps',nextProps)
// console.log('this.state: ', this.state)
// console.log('nextState',nextState)
// return true
// 外部传入的数据props有变化才重新渲染
for(let attr in nextProps){
// 最新的外部数据和当前的数据进行比对
if(nextProps[attr] !== this.props[attr]){
// 有一个不一样的就返回true,重新render
return true
}
}
for(let attr in nextState){
if(nextState[attr] !== this.state[attr]){
return true
}
}
return false
}
PureComponent
纯组件作用: 当组件中的props和state数据都没有变化的时候,组件不重新render
componentWillUnmount
: 组件即将卸载前执行
- 清除定时器
- 取消订阅
- 解绑事件监听
ref作用:在react组件中可以通过绑定ref,来获取原生的dom元素
- ref=‘字符串’
- ref=回调函数
- ref=
React.createRef()
import React, { Component, createRef } from 'react'
export default class App extends Component {
pwdRef = createRef()
render() {
return (
<>
App
{/* 方式一[不推荐] ref=字符串 */}
{/* 方式二[不推荐]:ref=回调函数 弊端:写法太麻烦 */}
{
console.log('el', el)
this.inputRef = el
}}/>
{/* 方式三[推荐]:React.createRef() */}
>
)
}
}
受控组件、非受控组件,说的都是表单元素
受控组件:表单元素的值,受到组件状态[state]的控制
1. 给表单元素绑定 value,值是 状态数据state 1. 绑定`onChange`回调函数,回调函数内部给state重新赋值 1. 获取受控组件的表单数据:读取状态数据state即可。(表单数据和state已经双向数据绑定了)
import React, { Component } from 'react'
/**
* react中研究表单
* 1. 状态数据如何渲染到表单元素身上
* 2. 如果获取最新的表单数据
*
*/
export default class FormControl extends Component {
submitHandler(e) {
// 阻止默认提交行为
e.preventDefault()
console.log('表单数据提交了')
console.log('username:', this.state.username)
console.log('pwd:', this.state.pwd)
console.log('sex:', this.state.sex)
console.log('city:', this.state.city)
console.log('hobby:', this.state.hobby)
}
state = {
username: 'atguigu',
pwd: '123123',
sex: "0", // 1 男 0 女
city: "2",
hobby: ["1"]
}
// usernameChange(e){
// console.log('username:', e.target.name) // username
// this.setState({
// [e.target.name]:e.target.value
// })
// }
// pwdChange(e){
// console.log('pwd:', e.target.name) // pwd
// this.setState({
// [e.target.name]:e.target.value
// })
// }
// sexChange(e){
// console.log('sex: ', e.target.name) // sex
// this.setState({
// [e.target.name]:e.target.value
// })
// }
changeHandler(e) {
let value = e.target.value
if(e.target.type === 'checkbox'){
let index = this.state[e.target.name].findIndex(item =>item === e.target.value)
value = [...this.state[e.target.name]]
if(index === -1) value.push(e.target.value)
else value.splice(index,1)
}
this.setState({
[e.target.name]: value
})
}
render() {
let { username, pwd, sex, city, hobby } = this.state
return (
)
}
}
非受控组件:只实现了单项数据绑定[ 从状态数据到视图显示 ]
- 绑定数据渲染:
defaultValue、defaultChecked
- 获取用户数据:
- 通过绑定ref的方式,通过真实
dom
元素,获取用户最新输入- 就涉及到数组的常用方法 find filter map
受控组件:获取数据简单【state】,但是将用户输入改变state复杂 [changeHandler]
非受控组件:不需要写changeHandler。获取用户最新输入的值复杂[需要运用数组方法获取]
import React, { Component,createRef } from 'react'
/**
* 非受控组件:
*
* 1. 状态数据如何渲染到页面
* 2. 如何收集用户输入的最新数据
*
*/
export default class FormOutControl extends Component {
state = {
username:'atguigu',
pwd:999,
sex:"1",
city:"3",
hobby:["2","3"]
}
usernameRef = createRef()
pwdRef = createRef()
sexRef = [createRef(), createRef()]
cityRef = createRef()
hobbyRef = [createRef(), createRef(), createRef(),createRef()]
submitHandler(e){
e.preventDefault()
console.log('表单提交了')
console.log('username: ', this.usernameRef.current.value.trim())
console.log('pwd: ', this.pwdRef.current.value.trim())
// 获取单选框用户最新输入
console.log('sex: ', this.sexRef.filter(item=>item.current.checked)[0].current.value)
console.log('sex: ', this.sexRef.find(item=>item.current.checked).current.value)
console.log('city: ', this.cityRef.current.value)
// 复选框
console.log('hobby: ', this.hobbyRef)
console.log('hobby: ', this.hobbyRef.filter(item=>item.current.checked))
console.log('hobby: ', this.hobbyRef.filter(item=>item.current.checked).map(item=>item.current.value))
}
render() {
let {username, pwd, sex, city, hobby} = this.state;
return (
)
}
}
编程思维:
组件化开发的开发思路:
拆分组件
组件化静态页面布局
首屏数据渲染
3-1. 需要什么样的状态数据?【数据结构】
3-2. 状态数据定义在哪个组件身上合适?
如果该数据只有一个组件使用,那么就放在这个组件身上
如果该数据需要多个组件使用,那么就放在他们共同的父级组件身上[状态提升]
为什么要状态提升?
因为多个组件需要同一个数据,就涉及到组件间通信,父子组件传递数据是最方便的
完成用户交互功能
- 用语言描述你要做什么
- 给描述的事情分步骤
- 给步骤罗列技术点
- 综合考量使用什么技术实现
TodoList
组件布局
- 创建对应组件的目录及文件
- 将整体html结构和整体的css,都拷贝到TodoList组件中,完成页面的正常显示
- 修改class ==>
className
渲染列表:必须是数组。列表中的数据信息比较多,所以数组的每一项都得是一个对象
根据静态页面效果,分析数据结构如下:
todos = [ { id:1 title:'吃饭' isDone:true }, { id:2, title:'睡觉', isDone:false } ]
状态提升
let {todos} = this.props let total = todos.length; let doneNum = todos.reduce((pre, cur)=>pre + cur.isDone,0)
语言描述要做什么【干什么->效果】:
在头部文本框输入内容,按回车后,可以渲染到列表中
拆分步骤:
- 文本框输入内容:
- 按回车
- 渲染到列表
根据步骤罗列技术点
- 获取文本框输入内容
- 非受控组件 ref:
- e.target:当时间源是文本框的时候,可以有限考虑使用
- 受控组件
- 按回车:
- 绑定键盘事件:
onKeyUp、onKeyDown
- 判断按的是不是回车: keycode === 13 或 code == ‘Enter’
- 输入的内容渲染到列表:
- 子传父: 数据在哪里,参数数据的方法就要定义在哪里
- 父组件中定义方法
- 方法通过属性传递给子组件
- 子组件调用该方法,传递数据
- 创建一个新的对象 加入到 todos数组中,重置状态即可
Header.jsx
- 绑定keyup时间
- 获取文本框输入内容 e.target.value
- 调用父组件方法 传递title
- 清空文本框
import React, { Component } from 'react'
import './index.css'
export default class Header extends Component {
// 通过ref获取
// titleRef = React.createRef()
// 通过受控组件获取
// state = {
// title: ''
// }
// changeHandler(e){
// this.setState({
// title:e.target.value
// })
// }
keyupHandler(e) {
if (e.code === 'Enter') {
// 通过ref获取:
// console.log('ref: ', this.titleRef.current.value.trim())
// 受控组件获取
// console.log('state: ', this.state.title)
// 1. 获取文本框输入的内容
let title = e.target.value.trim()
console.log(title)
// 2. 调用父组件中的 addTodo方法,传递title
this.props.addTodo(title)
// 3. 清空文本框
e.target.value = ''
}
}
render() {
return (
{/* */}
{/* */}
)
}
}
- 定义addTodo方法
- 获取header传递过来的title
- 拼接 todo对象
- 生成id : nanoid
- 安装:
npm i nanoid
- 导入:
import {nanoid} from 'nanoid'
- 生成:
nanoid()
- 添加并重置状态todos
- 传递给Header组件
// 添加todo
addTodo(title){
// 1. 获取子组件header中的输入内容title
console.log('TodoList : ',title)
// 2. 拼一个新的todo对象
const todo = {
id:nanoid(),
title,
isDone:false
}
// 3. 加入数组,并执行setState触发更新
this.setState({
todos:[todo, ...this.state.todos]
})
}
- 描述[干了什么-》效果]:点击删除按钮,删除当前事项
- 步骤:
- 点击按钮
- 删除该条记录
- 技术:
- 点击按钮
- 绑定单击事件: onClick
- 删除该条记录
- 获取当前记录id
- 将id传递到 todos数据所在组件[TodoList组件]
- 在TodoList组件中定义 删除方法,根据id删除
- 重置状态数据,刷新列表
注意:
- 方法定义在TodoList组件中,因为数据在哪儿,操作数据的方法就在哪儿
- 方法要从爷爷组件【TodoList】,传递给孙子组件Item,List做为桥梁
TodoList.jsx
定义 deleteById方法
- 接收到item组件传递的 id
- 根据id 过滤todos数据
- 重置状态todos
将
deleteById
传递给 List组件
// 根据id删除todo
deleteById(id){
console.log('TodoList: id', id)
// 根据id删除,其实就是保留下,id不相等的记录
let newTodos = this.state.todos.filter(item=>item.id !== id)
// 重置状态数据
this.setState({
todos:newTodos
})
}
接收TodoList传递的 deleteById 方法,直接传递给Item组件
render() {
let {todos,deleteById} = this.props
return (
{todos.map(todo=>- )}
)
}
接收deleteById方法
调用并传递id参数
render() {
let {todo,deleteById} = this.props
return (
)
}
- 描述功能:点击复选框,进行完成和未完成切换
- 步骤:
- 点击复选框
- 切换该条记录的完成未完成状态
- 技术点:
- 点击复选框:
- 定义onChange事件
- 切换该条记录的完成未完成状态
- 获取该条记录id
- 将变更切换的方法[checkedOne]定义在 TodoList组件身上
- 将该方法传递给item,执行,并传递id,还要传isDone最新的值
- 在方法 checkedOne 中
- 获取id,获取最新的checked值
- 遍历修改数组中对应id 的 isDone值为checked
- 重置状态todos
{/*
方式一
checkedOne(todo.id,!todo.isDone)}/>
*/}
// 方式二:
checkedOne(todo)}/>
// checkedOne切换 id条记录的 isDone属性
// 方式一:
// checkedOne(id, isDone){
// console.log('TodoList id: ', id)// 1
// console.log('isDone: ', isDone)// true
// let newTodos = this.state.todos.map(todo=>{
// // 如果是当前的todo,那么修改isDone
// if(todo.id === id){
// todo.isDone = isDone
// }
// return todo
// })
// this.setState({
// todos: newTodos
// })
// }
// 方式二:
checkedOne(todo){
todo.isDone = !todo.isDone
// console.log(this.state.todos)
this.setState({
todos:[...this.state.todos]
})
}
全选反选功能实现
- 获取最新的checked 值
- 将
todos
数组中每一个todo
的isDone
属性修改成 checked
checkedAll(isDone){
console.log('isDone: ', isDone)
// 使用isDone 去修改todos数组中每一个todo 的isDone属性即可
this.setState({
todos:this.state.todos.map(todo=>{
todo.isDone = isDone
return todo
})
})
// let newTodos = this.state.todos.map(todo=>{
// todo.isDone = isDone
// return todo
// })
// this.setState({
// todos:newTodos
// })
}
checkedOne(todo)}/>
过滤掉
isDone
是true的值,保留下isDone
是false的数据
deleteDone(){
this.setState({
todos:this.state.todos.filter(todo=>!todo.isDone)
})
}
localStorage
- 存储:
localStorage.setItem(key, value)
- 读取:
localStorage.getItem(key)
- 移除:
localStorage.removeItem(key)
- 清空:
localStorage.clear()
需求:
将todos数组存储在 localStorage中
:
- 存储:
JSON.stringify(数组)
- 读取:
JSON.parse(JSON格式字符串)
HOC:H higher O Order C component
高阶函数:函数的参数是函数或者函数的返回值是函数
高阶组件:组件的参数是组件或组件的返回值是组件
高阶组件作用:可以实现类组件代码的复用
/**
* 函数的参数是函数,或函数的返回值是函数,就是高阶函数
*
* const app = express()
*
* // 中间件
* app.use((req,res,next)=>{
*
* })
*
*/
function sum(a,b,c){
return a + b + c;
}
console.log(sum(1,2,3))
// 高阶函数sum
// 柯里化函数:参数分步骤传递
// 为什么分开传?
//
function sum2(a){
return function (b){
return function (c){
return a + b + c;
}
}
}
let res = sum2(1)(2)(3)
console.log('res: ', res)
/** 1000 5000 3000 50个亿 3000万
* 完事具备:资金 、 场地、 人员、 关系、 资源 、 粉丝、
* 啥也没有:1000、 5000 3000
*
*/
// 1.0
// function WithApp(Com){
// return Com
// }
import {Component} from 'react'
// 2.0
// function WithApp(Com){
// return class extends Component{
// render(){
// return
// }
// }
// }
// 3.0 属性代理
// function WithApp(Com){
// return class extends Component{
// render(){
// return
// }
// }
// }
// 4.0 属性代理
// function WithApp(Com,props){
// return class extends Component{
// render(){
// return
// }
// }
// }
// 5.0 参数分阶段传递
// function WithApp(props){
// return function (Com){
// return class extends Component{
// render(){
// return
// }
// }
// }
// }
// 6. 0 状态数据和props一起传递给子组件Com
// function WithApp(props){
// return function (Com){
// return class extends Component{
// state = {
// ...props,
// money:1000
// }
// render(){
// return
// }
// }
// }
// }
// 7.0
function WithApp(mapStateToProps){
return function (Com){
return class extends Component{
state = {
...mapStateToProps(),
money:1000
}
render(){
return
}
}
}
}
export default WithApp
高阶组件的作用:就是为了实现类组件的逻辑的复用
import { Component } from "react";
/**
*
* @param {*} WC W Wrapper C component
*/
export default function WithMove(WC) {
return class extends Component {
state = {
x: 120,
y: 60
}
moveHandler(e) {
this.setState({
x: e.clientX,
y: e.clientY
})
}
componentDidMount() {
window.addEventListener('mousemove', this.moveHandler.bind(this))
}
componentWillUnmount() {
window.removeEventListener('mousemove', this.moveHandler.bind(this))
}
render(){
return
}
}
}
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'
class TianE extends Component {
render() {
let {x,y} = this.props
return (
尚硅谷的女老师们!!!
)
}
}
export default WithMove(TianE)
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'
class HaMa extends Component {
render() {
let {x,y} = this.props
x += 110
y += 110
return (
晶哥!!!
)
}
}
export default WithMove(HaMa)
import React, { Component } from 'react'
import HaMa from './components/HaMa'
import TianE from './components/TianE'
export default class App extends Component {
render() {
return (
)
}
}
useState
给函数组件创建状态数据的
语法:
let [状态, 设置状态函数] = useState(初始值)
setXxx
函数的两种用法setXxx(要赋的值):
setXxx(回调函数):
应用:当使用方式一设置值不正确,发现无法获取到最新的数据时,可能是由于闭包导致的。所以,要考虑使用方式二
import React, {useEffect, useState} from 'react'
export default function App() {
let [msg, setMsg] = useState('atguigu')
useEffect(()=>{
// componentDidMount
setTimeout(()=>{
// 方式一:
// msg 只能取到初始值 atguigu, 并不能取到最新变化后的值
// setMsg(msg + '_')
// 方式二:
// 参数是一个回调函数,回调函数的参数,是最新的变化后的状态数据
// 回调函数的返回值,是设置的最新的状态数据的值
setMsg((msg)=>{
return msg + '_'
})
},2000)
},[])
return (
msg: {msg}
)
}
useEffect
作用:模拟生命周期函数
componentDidMount
componentDidUpdate
componentWillUnmount
注意:
useEffect
可以调用多次// 可以同时调用多次 useEffect(()=>{ console.log('didMount2') },[]) // 可以同时调用多次 useEffect(()=>{ console.log('didMount3') },[money])
用法一:只有第一个参数回调函数,没有第二个参数
useEffect(()=>{ // componentDidMount + componentDidUpdate[所有state的变化都触发 + 所有的props变化也触发]
console.log('用法一:run')
})
// 用法二: 第二个参数是一个空数组,单独模拟componentDidMount
// useEffect(()=>{ // componentDidMount
// console.log('用法二:')
// },[])
// 用法三: 第二个参数是数组,数组中指定监听的元素
// useEffect(()=>{ // componentDidMount + componentDidUpdate[监听谁 谁变化]
// console.log('用法三:')
// },[money,count])
// 用法四:同时模拟 componentDidMount- 挂载 和 componentWillUnmount-卸载
useEffect(()=>{
// componentDidMount()
console.log('挂载后触发')
return ()=>{
// 这里模拟的是 componentWillUnmount
console.log('componentWillUnmount - 模拟组件销毁是触发')
}
},[])
useRef
作用:创建ref对象,在函数组件中绑定获取真实
dom
元素用法:
引入:
import {useRef} from 'react'
创建:
const inputRef = useRef()
绑定:
获取:
inputRef.current
import React, { useRef, useState } from 'react'
export default function App() {
// 定义状态数据
let [username, setUsername] = useState('atguigu')
let [pwd, setPwd] = useState('123')
let [sex, setSex] = useState("1")
let [city, setCity] = useState('1')
let [hobby, setHobby] = useState(["1","3"])
const submitHanlder = (e)=>{
e.preventDefault()
}
// function usernameChange(e){
// console.log(e.target.value)
// setUsername(e.target.value)
// }
// function pwdChange(e){
// setPwd(e.target.value)
// }
function changeHandler(fn){
return (e)=>{
let value = e.target.value
if(e.target.type === 'checkbox'){
let index = hobby.findIndex(item=>item === e.target.value)
value = [...hobby]
if(index === -1)value.push(e.target.value)
else value.splice(index, 1)
}
fn(value)
}
}
return (
)
}
import React, { useRef, useState } from 'react'
export default function App() {
// 定义状态数据
let [username, setUsername] = useState('atguigu')
let [pwd, setPwd] = useState('123')
let [sex, setSex] = useState("1")
let [city, setCity] = useState('1')
let [hobby, setHobby] = useState(["1","3"])
// 定义ref
const divRef = useRef()
const usernameRef = useRef()
const pwdRef = useRef()
const sexRef = [useRef(), useRef()]
const cityRef = useRef()
const hobbyRef = [useRef(), useRef(), useRef(), useRef()]
const submitHanlder = (e)=>{
e.preventDefault()
console.log('非受控获取提交数据')
console.log('username: ', usernameRef.current.value.trim())
console.log('pwd: ', pwdRef.current.value.trim())
console.log('sex: ', sexRef.find(item=>item.current.checked).current.value)
console.log('city: ', cityRef.current.value)
console.log('hobby: ', hobbyRef.filter(item=>item.current.checked).map(item=>item.current.value))
}
return (
App
div
)
}
- React钩子函数,不能在类组件中调用
- React Hooks必须在React函数组件或自定义React Hook函数中调用
- hook函数使用时数量必须是确定的【要在顶级作用域中使用】
import React, { useState,Component } from 'react'
function App() {
let [count,setCount] = useState(10)
// 报错:hook函数不能在普通函数中使用
// function addTodo(){
// let [msg, setMsg] = useState('atguigu')
// }
// 不能在循环、判断中使用,必须是确定数量的【完全相同的顺序】
// if(true){
// let [msg, setMsg] = useState('atguigu')
// }
// for(let i=0;i<100;i++){
// let [msg, setMsg] = useState('atguigu')
// }
// if(count == 1){
// return
// }
// let [msg, setMsg] = useState('atguigu')
return (
App
)
}
// 报错: React Hook "useState" cannot be called in a class component. React钩子“useState”不能在类组件中调用
// React Hooks must be called in a React function component or a custom React Hook function React Hooks必须在React函数组件或自定义React Hook函数中调用
//
// class App extends Component{
// render(){
// let [msg, setMsg] = useState('atguigu')
// return (
// App
// )
// }
// }
export default App
hook 本质就是一个函数,hook函数和普通函数有什么区别呢?
区别在函数名
hook函数:函数名必须是以
useXxx
开头的。 例如:usePostion、useMyHook
import { useEffect, useState } from "react";
/**
* 遵循 useXxx命名法,react就认为是hook函数。
* 在自定义hook中使用 其他hook,不报错
*/
export default function useMyHook(){
let [x, setX] = useState(0)
useEffect(()=>{
console.log('sjdfjlkas')
})
}
/**
* 既不是函数组件【首字母没大写】、也不是其他hook【没有use开头】
* 所以,内部不能使用 其他hook
*/
// export default function myHook(){
// let [x, setX] = useState(0)
// useEffect(()=>{
// console.log('sjdfjlkas')
// })
// }
祖先组件向后代组件传参
使用步骤:
创建并导出 context:
export default React.createContext()
使用
context.Provider
组件包裹后代组件,并传递数据
后代组件 在后代组件中获取数据
引入context:
import context from './context.js'
使用useContext获取值
let {数据} = useContext(context)
import React from 'react'
// 创建一个context对象并导出
export default React.createContext()
import React from 'react'
import Father from './components/Father'
import context from './context'
export default function App() {
return (
{/* 使用context中的Provider组件包裹,后代组件,并传递数据value */}
App
)
}
import React, { useContext } from 'react'
import Son from './Son'
// 导入context对象
import context from '../context'
export default function Father() {
let { username } = useContext(context)
return (
Father
username: {username}
)
}
pubsub
作用:实现任意组件间的通信
原理:利用发布订阅原理
使用:
安装:
npm i pubsub-js
导入:
import PubSub from 'pubsub-js'
Api
- 订阅消息:
let 消息id = PubSub.subscribe(消息名, (消息名, 数据)=>{})
- 发布消息:
PubSub.publish(消息名, 数据)
- 取消订阅:
- 取消自己的订阅:
PubSub.unsubscribe(消息id)
- 取消该消息名的所有消息:
PubSub.unsubscript(消息名)
- 取消所有消息:
clearAllSubscriptions
- 安装axios
- 配置:
- 使用:
src/request/index.jsx 对axios
进行全局配置// 2. 导入axios
import axios from 'axios'
// 3. 配置axios
const request = axios.create({
baseURL: 'https://api.github.com/',
timeout: 20000
})
// 4. 配置响应拦截器-简化响应的数据
request.interceptors.response.use(response=>response.data)
export default request
import React, { useEffect } from 'react'
import request from './request'
export default function App() {
// 一般首屏数据渲染,会在componentDidMount生命周期中发送ajax请求
useEffect(() => {
// useEffect的回调不能用async修饰,需要单独定义一个async函数并调用
async function getRepo() {
let res = await request.get('/search/repositories', {
params: {
q: 'vue',
sort: 'stars'
}
})
console.log('res: ', res)
}
getRepo()
}, [])
return (
App
)
}
大型项目,组件间进行数据通信,数据的管理和维护比较麻烦。我们希望对数据进行统一的管理。使用redux
redux: 统一进行数据状态管理的库。
安装:npm install @reduxjs/toolkit
导入:import {createSlice} from '@reduxjs/toolkit'
创建 切片
// 1. 安装 :
// 2. 导入 createSlice 模块
// create 创建
// Slice 切片【模块】
import {createSlice} from '@reduxjs/toolkit'
// 3. 创建切片
const countSlice = createSlice({
// 切片名
name:'count',
// initial 初始化的 state状态,初始化的状态数据:值一般是一个对象
initialState:{
num:1000,
msg:'atguigu'
},
// 干活的程序员[ 函数 ]
// 每在reducers对象中创建一个 reducer,就会自动在 切片上的actions属性上增加一个同名方法。
// 该方法的身份是actionCreator,作用是用来创建action对象的
reducers:{
/**
*
* @param {*} state :状态数据
* @param {*} action : 行为。===> {type:'切片名/方法名', payload:数据}
* type:属性给程序看的,不是给程序员使用的
*/
addNum: (state, action)=>{
state.num += action.payload
},
decNum:(state,action)=>{
state.num -= action.payload
}
}
})
console.log('countSlice: ',countSlice)
// 解构出 actionCreator : addNum
const {addNum} = countSlice.actions
let res = addNum(1000) // 使用addNum actionCreator 创建一个action
console.log('res: ', res) // {type: 'count/addNum', payload: 1000}
// 1. 安装 :
// 2. 导入 createSlice 模块
// create 创建
// Slice 切片【模块】
// : 创建数据仓库的模块
import {createSlice, configureStore} from '@reduxjs/toolkit'
// 3. 创建切片
const countSlice = createSlice({
// 切片名
name:'count',
// initial 初始化的 state状态,初始化的状态数据:值一般是一个对象
initialState:{
num:1000,
msg:'atguigu'
},
// reducers中定义:干活的程序员[ 函数 ]
// 每在reducers对象中创建一个 reducer,就会自动在 切片上的actions属性上增加一个同名方法。
// 该方法的身份是actionCreator,作用是用来创建action对象的
reducers:{
/**
*
* @param {*} state :状态数据
* @param {*} action : 行为。===> {type:'切片名/方法名', payload:数据}
* type:属性给程序看的,不是给程序员使用的
*/
addNum: (state, {payload})=>{
state.num += payload
},
decNum:(state,action)=>{
state.num -= action.payload
}
}
})
// 以下是打印测试切片的代码
// console.log('countSlice: ',countSlice)
// // 解构出 actionCreator : addNum
const {addNum} = countSlice.actions
// let res = addNum(1000) // 使用addNum actionCreator 创建一个action
// console.log('res: ', res) // {type: 'count/addNum', payload: 1000}
// 创建仓库 store
const store = configureStore({
reducer:{
count:countSlice.reducer
}
})
// 可以对状态数据进行监听,如果有变化,就触发回调函数的执行
store.subscribe(()=>{
console.log('订阅数据变了')
console.log(store.getState())
})
// 查看状态数据
// console.log(store.getState())
// 修改数据
// dispatch: 分发,参数是一个action对象,根据action对象中的 type,来找到对应切片中的对象应发完成任务
store.dispatch(addNum(30))
store.dispatch(addNum(50))
store.dispatch(addNum(100))
/**
* 使用redux 管理商品数据
* 1. 可以添加商品数据
*
*/
import {createSlice, configureStore} from '@reduxjs/toolkit'
// 1. 创建切片
const goodsSlice = createSlice({
name:'goods',
initialState:{
goodsList:[]
},
reducers:{
addGoods(state, {payload}){
// 实现向goodsList数组中添加数据
// console.log('payload', payload)
let goods = {
...payload,
// 0-9 a-z = 36
// 0.1231532234547643
id: Math.random().toString(36).slice(2)
}
// console.log('goods', goods)
state.goodsList.push(goods)
}
}
})
// 2. 创建仓库
const store = configureStore({
reducer:{
goods: goodsSlice.reducer
}
})
// 3. 监听仓库
store.subscribe(()=>{
console.log('数据: ', store.getState())
})
// 4. 添加商品
// 获取actionCreator
const {addGoods} = goodsSlice.actions
// {
// gname:'商品名',
// price:1999
// }
store.dispatch(addGoods({gname:'小米手机', price:1999}))
store.dispatch(addGoods({gname:'华为笔记本', price:7000}))
src
|- store 整个redux数据目录
| |- slice 所有切片目录
| |- index.js 创建的store仓库入口文件
import { createSlice } from '@reduxjs/toolkit'
// 1. 创建切片
const goodsSlice = createSlice({
name: 'goods',
initialState: {
goodsList: []
},
reducers: {
addGoods(state, { payload }) {
// 实现向goodsList数组中添加数据
// console.log('payload', payload)
let goods = {
...payload,
// 0-9 a-z = 36
// 0.1231532234547643
id: Math.random().toString(36).slice(2)
}
// console.log('goods', goods)
state.goodsList.push(goods)
}
}
})
/**
* 一个切片,大体暴露以下几类东西
* 1. 默认暴露: reducer
* 2. 分别暴露:actionCreator:
* 3. 分别暴露:异步操作的方法
*/
export default goodsSlice.reducer
export const {addGoods} = goodsSlice.actions
import {configureStore} from '@reduxjs/toolkit'
import goods from './slice/goodsSlice'
// 2. 创建仓库
const store = configureStore({
reducer: {
goods
}
})
export default store
// 1. 导入仓库
import store from "./store";
// 2. 导入actionCreator
import {addGoods} from './store/slice/goodsSlice'
store.subscribe(()=>{
console.log('数据: ', store.getState())
})
// 2. 添加商品
store.dispatch(addGoods({
gname:'小米手机',
price:1999
}))
store.dispatch(addGoods({
gname:'华为手机',
price:5999
}))
需求:
点击按钮发送ajax请求,获取一句话,将一句话的长度累计到 redux状态数据
文件介绍:
- request/index.js: axios 统一配置文件【基础路径、响应拦截器简化返回数据】
- api/onSentence.js: 所有跟一句话相关的请求接口方法
- store: 数据仓库
- store/slice/countSlice:
- 定义状态数据num
- 使用createAsyncThunk 创建了异步的actionCreate
- 调用了api接口
- extraReducer中,添加了case,处理请求后的数据
- App.jsx
- 展示状态数据
- 点击按钮,触发异步的actionCreator方法
- src/index.js
- 导入Provider
- 包裹根组件,传递store数据
todos数据来源于数据库,在前端使用redux进行管理。因此,需要保证远端服务器数据和redux中的数据是同步的。因此,在进行用户交互操作的时候, 如果改动了数据,需要修改远端数据库的数据,也要修改redux中管理的数据。
- 修改远端服务器数据:在异步的actionCreator中进行
- 修改redux中的数据:在addCase中进行
import axios from 'axios'
// 配置baseURL和超时时间
const request = axios.create({
baseURL:'http://localhost:7000',
timeout:20000
})
// 响应拦截器,简化服务器端返回数据
request.interceptors.response.use(response=>response.data)
export default request
src/api/todos.js
根据后端接口文档【或源码】,封装前端发送ajax的请求函数
import request from '../request'
/**
* 获取素有的todolist数据
* @returns
*/
export const getTodos = ()=>{
return request.get('/todos')
}
/**
* 添加todo
* @param {*} todo {title:xxx,isDone:false}
* @returns
*/
export const addTodo = (todo)=>{
return request.post('/todos', todo)
}
/**
* 根据id删除todo
* @param {*} _id
* @returns
*/
export const deleteById = (_id)=>{
return request.delete('/todos/' + _id)
}
/**
* 根据id 修改isDone的值
* @param {*} todo {_id:xxx, title:xxx, isDone:false}
* @returns
*/
export const updateIsDone = (todo)=>{
return request.patch('/todos/'+ todo._id, todo)
}
/**
* checkAll ,全选反选
* @param {*} data {isDone: false/true}
* @returns
*/
export const checkAll = (data)=>{
return request.patch('/checkAll', data)
}
/**
* 清除已完成
* @returns
*/
export const deleteChecked = ()=>{
return request.get('/deleteChecked')
}
获取服务器端 todoList数据,初始化redux中的状态数据。在组件中读取 redux中的todos数据,进行渲染
import { configureStore } from "@reduxjs/toolkit";
import todoList from './slice/todoSlice'
const store = configureStore({
reducer:{
todoList
}
})
export default store
store/slice/todoSlice.js
- 定义异步的actionCreator: 发送ajax请求,获取所有todos数据
- addCase: 将请求回来的todos数据,初始化redux中的 状态数据 todos
import {createSlice, createAsyncThunk} from '@reduxjs/toolkit'
// 导入api方法
import {getTodos} from '../../api/todos'
const todoSlice = createSlice({
name:'todoList',
initialState:{
todos:[]
},
extraReducers:builder=>{
// 初始化todos
builder.addCase(asyncGetTodos.fulfilled, (state, {payload})=>{
state.todos = payload
})
}
})
/**
* 发送ajax请求 初始化 todos
*/
export const asyncGetTodos = createAsyncThunk('todoList/getTodo', async (payload)=>{
let {todos} = await getTodos()
return todos
})
export default todoSlice.reducer
导入Provider,包裹App根组件, 传递store
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import store from './store'
// // 导入App组件
import App from './App'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<App />
</Provider>
)
export default function App() {
const dispatch = useDispatch()
useEffect(()=>{
// 初始化todos
dispatch(asyncGetTodos())
},[])
return (
<>
>
)
}
import React from 'react'
import { useSelector } from 'react-redux'
import Item from '../Item/Item'
export default function List() {
let {todos} = useSelector(state=>state.todoList)
return (
{todos.map(todo=>- )}
)
}
useRef:
绑定dom元素,获取真实dom
可以单独模拟componentDidUpdate
给以给类组件绑定,获取类组件的实例对象
给函数组件绑定:
4-1. 本身不支持
4-2. 函数组件使用 React.forwardRef 包裹再导出
4-2-1. 函数组件的第二个参数,可以收到父组件的ref对象
4-2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法
import React, { useRef } from 'react'
import { useEffect } from 'react'
import ClassTest from './components/ClassTest'
import FunTest from './components/FunTest'
/**
* 1. 绑定dom元素,获取真实dom
* 2. 可以单独模拟componentDidUpdate
* 3. 给以给类组件绑定,获取类组件的实例对象
* 4. 给函数组件绑定:
* 1. 本身不支持
* 2. 函数组件使用 React.forwardRef 包裹再导出
* 2-1. 函数组件的第二个参数,可以收到父组件的ref对象
* 2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法
*
*/
export default function App() {
let flagRef = useRef(true)
console.log(flagRef)
useEffect(() => { // componentDidMount + componentDidUpdate
if (flagRef.current) {
// didMount
flagRef.current = false
return;
}
// didUpdate
})
// 类组件ref
let classRef = useRef()
// 函数组件ref
let funRef = useRef('fun')
return (
类组件
函数组件
{/* 函数组件本身不能绑定ref */}
{/*
可以使用React.forwordRef处理函数组件后,进行ref的绑定
可以在父组件中操作子组件的真实dom元素
*/}
)
}
import React, { Component } from 'react'
export default class ClassTest extends Component {
state = {
msg:'类组件'
}
render() {
return (
ClassTest
)
}
}
import React,{useImperativeHandle, useRef} from 'react'
function FunTest(props,ref) {
console.log('ref: ', ref)
// 子组件自己的ref
let selfRef = useRef()
useImperativeHandle(ref, ()=>({
changeColor:()=>{
selfRef.current.style.color = 'blue'
},
changeSize:()=>{
selfRef.current.style.fontSize = '100px'
}
}))
return (
FunTest
)
}
export default React.forwardRef(FunTest)
模拟一个小型的 redux,做简易版的集中状态管理
import React from 'react'
import Test1 from './components/Test1'
export default function App() {
return (
)
}
import React, { useReducer } from 'react'
const initialState = { count: 0, msg: '哈哈' }
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
...state,
count: state.count + 1,
}
case 'decrement':
return {
...state,
count: state.count - 1,
}
default:
throw new Error()
}
}
export default function Test1() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: {state.count}
msg: {state.msg}
>
)
}
避免组件更新,函数重新创建。
setXxx 时,要使用 回到函数,获取到最新的状态数据
import React, { useCallback, useState } from 'react'
/**
* useCallBack 可以缓存函数,避免组件更新,函数重新创建
*
*/
export default function App() {
let [count, setCount] = useState(100)
function addCount(){
setCount(count=>{
return count + 1
})
}
const handle = useCallback(addCount, [])
return (
count: {count}
)
}
缓存一个函数运行的结果
import { useMemo, useState } from "react";
export default function After() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const expensive = useMemo(() => {
console.log('compute');
let sum = 0;
for (let i = 0; i < count * 100; i++) {
sum += i;
}
return sum;
}, [count]);
return
{count}-{expensive}
{val}
setValue(event.target.value)}/>
;
}
类似类组件的纯组件功能,当自身状态数据state和外部数据props没有变化的时候,不重新渲染
import React, { useState,Component } from 'react'
import Son from './components/Son'
/**
*
* 函数组件自身状态数据没有变化,已经做过优化,只多渲染一次
*/
export default class App extends Component {
state = {
count:100
}
render(){
let {count} = this.state
console.log('app render')
return (
count:{count}
)
}
}
import React from 'react'
import { useState } from 'react'
/**
* 函数组件对state,自身状态数据,不变时,只多渲染一次
* 函数组件没有对props数据不变的情况做优化
*
*/
function Son({count}) {
let [msg, setMsg] = useState('atguigu')
console.log('son render')
return (
props-count: {count}
state-msg: {msg}
)
}
export default React.memo(Son)
优化页面呈现,避免出现位置移动的闪屏
import React, { useEffect, useLayoutEffect, useRef } from 'react'
import TweenMax from 'gsap' // npm i [email protected]
import './index.css'
const Animate = () => {
const REl = useRef(null)
useLayoutEffect(() => {
/*下面这段代码的意思是当组件加载完成后,在0秒的时间内,将方块的横坐标位置移到600px的位置*/
TweenMax.to(REl.current, 0, { x: 600 })
}, [])
return (
square
)
}
export default Animate
.square{
width:100px;
height:100px;
border:2px solid red;
}
{
"react模板":{
"prefix": "!react",
"body": [
"",
"",
"",
"\t",
"\tTitle ",
"\t",
"\t",
"\t",
"",
"",
"\t",
"",
"",
""
],
"description": "快速构建react模板页页面"
}
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="./lib/react.development.js">script>
<script src="./lib/react-dom.development.js">script>
head>
<body>
<div id="root">div>
<div class="box">div>
body>
<script>
{
// 1. 可以在同一个页面有多个容器
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('root容器')
// const box = ReactDOM.createRoot(document.querySelector('.box'))
// box.render('box 容器')
}
{
// 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('首次渲染')
// root.render('二次渲染')
}
{
// 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root1.render('root 一')
// root2.render('root 二')
}
{
// 4. 不能将 html body 指定为 react容器
// html
// const root = ReactDOM.createRoot(document.documentElement);
// root.render('html')
//
// const root = ReactDOM.createRoot(document.body);
// root.render('body')
}
{
// 5. 支持链式调用
ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
}
script>
html>
<script>
/**
* react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
* 1. 页面是如何渲染的?
* 地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
* 1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
* 1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
* 1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
*
* 2. 什么是重排和重绘?
* css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
*
* 3. 传统DOM操作的缺陷!
* 频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
* this.style.height = 100px;
* this.style.left = '200px'
*
* 4. React、vue 性能更好,为什么?
* React 和 vue引入了虚拟dom 优化性能
*
* 5. 虚拟DOM是什么?
* 1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
* 2. 虚拟DOM 通过 render 可以转化为真实DOM
* 3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
* 4. 虚拟dom 在react中,我们又称之为时 react元素
* 5. 虚拟dom的创建有两种方法
* 1. 通过 React.createElement() 创建
* 2. 通过 jsx 创建
*
*/
script>
call apply 立即执行
bind:返回一个新函数,不执行
let obj = {name:'atguigu'}
function getSonData(a){
console.log('this: ',this)
}
// call apply 改变this指向,函数会立即执行
getSonData.call(obj,10)
getSonData.apply(obj,[1])
// bind: 返回一个新的函数,新函数的this指向已经改变。不会立刻执行
let fn = getSonData.bind(obj,20)
fn()
rfc: react function component
rcc: react class component
普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是this指向只跟谁调用的有关[.前面是谁],跟在哪定义,在哪调用无关。
直接赋值箭头函数:相当于是在构造函数中赋值,因为是箭头函数,所以没有自己的this,因此方法中的this永远指向组件的实例对象【constructor中的this永远指向组件实例】。所以,直接赋值箭头函数,this 永远指向组件实例,跟在哪儿调用,谁调用没关系
class Person{
constructor(name,age){
this.name = name;
this.age = age;
// this.eat = ()=>{
// console.log(this)
// console.log(this.name + '想吃饭了')
// }
// this.state = {
// count:1
// }
}
// say方法是普通方法【this只跟谁调用的有关,跟在哪里定义在哪里调用无关】
say(){
console.log('say this: ', this)
console.log('我的名字是: ' + this.name + ', 我的年龄是: ' + this.age)
}
// 通过=号赋值,定义箭头函数
// eat 中的this 只跟在哪里定义的有关,跟在哪里调用无关,谁调用的都没关系
eat = ()=>{
console.log('eat this: ', this)
console.log(this.name + '想吃饭了')
}
state = {
count:1
}
}
const p1 = new Person('yuonly','10')
// p1.say()
const p2 = new Person('atguigu',19)
// p2.say()
p1.eat()
p2.eat()
//console.log('say: ',p1.say === p2.say) // true say方法在 Perons.prototype.say
//console.log('eat: ',p1.eat === p2.eat)// false 每一个实例对象都有一个自己的eat方法
let obj = {
name:'ll',
f1:p1.say,
f2:p1.eat
}
// obj.f1() // 我的名字是: ll, 我的年龄是: undefined
// say 方法中的this,谁调用this指向谁。
obj.f2()
## 1-3. 注意事项
```html
<!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="box"></div>
</body>
<script>
{
// 1. 可以在同一个页面有多个容器
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('root容器')
// const box = ReactDOM.createRoot(document.querySelector('.box'))
// box.render('box 容器')
}
{
// 2. 同一个容器可以进行多次渲染,后面的结果会覆盖前面的
// const root = ReactDOM.createRoot(document.querySelector("#root"));
// root.render('首次渲染')
// root.render('二次渲染')
}
{
// 3. 同一个容器不允许被多次指定为react容器,没有报错,会弹警告。不建议这样操作
// const root1 = ReactDOM.createRoot(document.querySelector("#root"));
// const root2 = ReactDOM.createRoot(document.querySelector("#root"));
// root1.render('root 一')
// root2.render('root 二')
}
{
// 4. 不能将 html body 指定为 react容器
// html
// const root = ReactDOM.createRoot(document.documentElement);
// root.render('html')
//
// const root = ReactDOM.createRoot(document.body);
// root.render('body')
}
{
// 5. 支持链式调用
ReactDOM.createRoot(document.querySelector('#root')).render('链式调用')
}
</script>
</html>
<script>
/**
* react vue 用到的页面渲染效率更高,因为都引入了虚拟dom的概念!
* 1. 页面是如何渲染的?
* 地址栏中输入网址按下回车,会发送一个请求,请求之后会得到响应的响应结果
* 1-1. 响应一个html页面:浏览器的html解析器会对html进行解析,并生成 DOM 树
* 1-2. 响应一个css, 浏览器的css解析器会对css进行解析,生成一个css的规则树
* 1-3. 响应一个js, js有可能会操作 dom 也有可能会操作 css样式,会造成重排和重绘
*
* 2. 什么是重排和重绘?
* css中的一些几何属性【位置、大小、宽高】,一旦发生变化,浏览器会将几何属性重新计算,因为几何属性不光影响自己,还会影响周围元素。重新计算的过程就叫重排。重排之后就会进行重绘。渲染的过程就叫重绘。重排必然导致重绘
*
* 3. 传统DOM操作的缺陷!
* 频繁进行dom操作,导致大量重排和重绘,非常有影响性能!
* this.style.height = 100px;
* this.style.left = '200px'
*
* 4. React、vue 性能更好,为什么?
* React 和 vue引入了虚拟dom 优化性能
*
* 5. 虚拟DOM是什么?
* 1. 虚拟DOM就是一个对象,与真实DOM的属性一一对应
* 2. 虚拟DOM 通过 render 可以转化为真实DOM
* 3. 虚拟DOM 是一个轻量级的对象,属性比真实dom少,只要需要的指定属性
* 4. 虚拟dom 在react中,我们又称之为时 react元素
* 5. 虚拟dom的创建有两种方法
* 1. 通过 React.createElement() 创建
* 2. 通过 jsx 创建
*
*/
script>
call apply 立即执行
bind:返回一个新函数,不执行
let obj = {name:'atguigu'}
function getSonData(a){
console.log('this: ',this)
}
// call apply 改变this指向,函数会立即执行
getSonData.call(obj,10)
getSonData.apply(obj,[1])
// bind: 返回一个新的函数,新函数的this指向已经改变。不会立刻执行
let fn = getSonData.bind(obj,20)
fn()
rfc: react function component
rcc: react class component
普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是this指向只跟谁调用的有关[.前面是谁],跟在哪定义,在哪调用无关。
直接赋值箭头函数:相当于是在构造函数中赋值,因为是箭头函数,所以没有自己的this,因此方法中的this永远指向组件的实例对象【constructor中的this永远指向组件实例】。所以,直接赋值箭头函数,this 永远指向组件实例,跟在哪儿调用,谁调用没关系
class Person{
constructor(name,age){
this.name = name;
this.age = age;
// this.eat = ()=>{
// console.log(this)
// console.log(this.name + '想吃饭了')
// }
// this.state = {
// count:1
// }
}
// say方法是普通方法【this只跟谁调用的有关,跟在哪里定义在哪里调用无关】
say(){
console.log('say this: ', this)
console.log('我的名字是: ' + this.name + ', 我的年龄是: ' + this.age)
}
// 通过=号赋值,定义箭头函数
// eat 中的this 只跟在哪里定义的有关,跟在哪里调用无关,谁调用的都没关系
eat = ()=>{
console.log('eat this: ', this)
console.log(this.name + '想吃饭了')
}
state = {
count:1
}
}
const p1 = new Person('yuonly','10')
// p1.say()
const p2 = new Person('atguigu',19)
// p2.say()
p1.eat()
p2.eat()
//console.log('say: ',p1.say === p2.say) // true say方法在 Perons.prototype.say
//console.log('eat: ',p1.eat === p2.eat)// false 每一个实例对象都有一个自己的eat方法
let obj = {
name:'ll',
f1:p1.say,
f2:p1.eat
}
// obj.f1()
// say 方法中的this,谁调用this指向谁。
obj.f2()