1. React基础

1-1. react-调试插件安装

浏览器右上角 … ===> 更多工具 ===> 扩展程序 ==> 开启开发者模式 ==> 将调试插件包拖拽进来即可

1-2. react 基本使用

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>Document</title><!-- 提供了 React对象,React核心对象 --><script src="./lib/react.development.js"></script><!-- 提供了ReactDOM 对象,专门用来操作DOM DOM三个字母都是大写的--><script src="./lib/react-dom.development.js"></script>
</head>
<body><!-- react 操作的舞台 --><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>

1-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><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>

1-4. 虚拟DOM

<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>

1-5. createElement 创建 react元素

语法: React.createElement(标签名, 属性, 子元素1, 子元素2 .....)

1-5-1. 基本使用

const root = ReactDOM.createRoot(document.querySelector("#root"));{// 语法:React.createElement(标签名, 属性, 子元素1, 子元素2 .....)// 1. 创建虚拟domconst 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)}

1-5-2. 没有属性的元素标签,使用null 、undefined、{}占位

// 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)

1-5-3. 特殊属性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)

1-5-4. 创建复杂结构-元素嵌套-及其问题

/*** *  当标签套标签时,就需要进行 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)

2. jsx

jsx = js + xml

作用:可以快速创建react元素【虚拟dom】

xml: 是一种数据结构,是json格式的数据出现之前,使用的通信数据结构

<user><name>atuigu</name><age>10</age>
</user>let user = {"name":"atuigu","age":10
}

2-1. jsx语法在html文件中解析

需要两个条件

  1. 引入 babel ,因为浏览器本身不认识jsx
  2. script标签加上type=“text/babel”
<!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"));// const div = React.createElement('div',null,'div元素')// root.render(div)// jsx 创建react元素// 浏览器本身不认jsx语法,需要进行语法转化,使用babel进行转化let div = <div>div元素</div>root.render(div)
</script>
</html>

2-2. jsx基本使用

  • jsx 语法书写非常像html标签,但本质是 react元素。最终在底层会转化为 React.createElement 的语法。

  • 构建复杂结构页面时,使用 () 括起来,可以进行换行

<!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><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>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>

2-3. jsx标签注意事项

jsx 标签只能写两种:

  1. html标签: 全部用小写
  2. 组件标签:首字母大写
const root = ReactDOM.createRoot(document.querySelector("#root"));
/***  注意事项:*  1. jsx 语法标签名如果是html元素标签,要用小写*  2. jsx 只能写html标签,写别的不报错。但是 react元素最终会render转化为真实dom。*     写别的能转化也能渲染,但是浏览器不认识,没有意义*  3. jsx除了写浏览器认识的标签,首字母大写的标签,会当做组件来处理* *      <Student />   === > Student 是一个组件*      <Header />    === > Header 组件* */
root.render((<div><username>yuonly</username><age>age</age></div>
))

2-4. jsx中的插值表达式

2-4-1. 渲染值基本使用

语法: { 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; //渲染123return 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:使用插值语法,什么都不渲染*/}{/*<p>对象: {{username:'atuigu',age:'10'}}</p> 对象:直接报错,对象不是一个合法的react子元素*/}<p>数组: {[1, 2, 3, 4]}</p>{/*将数组中的每一个元素遍历出来,挨个进行渲染*/}{/*以下代码会报错,因为会把数组中每一个元素拿出来渲染,发现对对象,渲染不了*/}{/*<p>数组中的元素是对象呢?{[{id:1,name:'atguigu'},{id:2,name:'yuonly'}]}</p>*/}{/*<p>函数: {fn}</p>   函数的定义也无法正常渲染 */}<p>函数的返回值: {fn()}</p><p>三元表达式: {flag === 1 ? '真' : '假'}</p><p>逻辑&& 运算: {5 && 8}</p><p>逻辑|| 运算: {5 || 8}</p></div>))
</script></html>

2-4-2. 条件渲染

  1. 单分支: 如果 a成立,那么…

    1. sex === ‘男’ && ‘boy’
    2. sex !== ‘男’ || ‘boy’
    3. if(sex===‘男’) ‘boy’
  2. 双分支的: 如果a成立,xxx,否则,

    1. xxx if(){}else{}
    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><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"));/***  条件渲染:*  1. 单分支:   如果 a成立,那么....          sex === '男' && 'boy'    sex !== '男' || 'boy'    if(sex==='男') 'boy'*  2. 双分支的: 如果a成立,xxx,否则,xxx   if(){}else{}              三元表达式*  3. 多条件的: 封装成一个函数处理* * *  需求:*   如果sex 是男,那么么显示男同学,否则显示女同学* */const sex = 1; // 1 表示男  0 表示女// 方式一:// const vdom = (//     sex === 1 ? <div>男</div> : <div>女</div>// )// 方式二:// let vdom;// if(sex===1){//     vdom = <div>男</div>// }else{//     vdom = <div>女</div>// }// root.render((//    vdom// ))// 单分支逻辑运算let flag = 2;// 1 阴性 2 阳性 // 方式一:// let vdom;// if (flag === 2) {//     vdom = <div>不许进入</div>// }// 方式二: 逻辑运算// let vdom = flag === 2 && <div>不许进入</div>// let vdom = flag !== 2 || <div>不许进入</div>// root.render((//     vdom// ))// 多分支条件渲染/*** 90分以上:优秀毕业生* 75分以上:学的不错* 60分以上:及格* 40分以上:仍需努力,还有救* 0以上   :洗洗睡吧*/function getLevel(score){if(score>=90) return '优秀毕业生'if(score>=75) return '学的不错'if(score>=60) return '及格'if(score>=40) return '仍需努力,还有救'if(score>=0) return '洗洗睡吧'}let vdom = (<div>{getLevel(30)}</div>)root.render((vdom))
</script></html>

2-4-3. 插值表达式赋值属性

  1. 表达式外部没有 “”
  2. 自定义属性也可以赋值
  3. 可以使用… 扩展运算进行批量赋值
const root = ReactDOM.createRoot(document.querySelector("#root"));const width = 300const height = 100const styles = {width:500,height:150,school:'atguigu'}root.render((<div><div width='200' height='200'>123123</div>{/*一:属性可以用插值表达式赋值:表达式外部不要有""号 */}<table border='1' width={width} height={height} id="d1" className="c1"><tr><td>id</td><td>name</td><td>操作</td></tr></table>{/*二:自定义属性也可以用插值表达式赋值 */}<table border='1' width={styles.width} height={styles.height} school={styles.school} id="d1" className="c1"><tr><td>id</td><td>name</td><td>操作</td></tr></table>{/*三:使用扩展运算,批量进行属性赋值 */}<table border='1' {...styles} id="d1" className="c1"><tr><td>id</td><td>name</td><td>操作</td></tr></table></div>))

2-5. 样式处理

2-5-1. style 行内样式

行内样式使用 style属性,属性值必须是一个对象

  1. style的值必须是一个对象:第一个{}表示的是插值表达式的边界, 第二个{}表示对象的边界
  2. 如果样式的单位是px,那么px可以省略,值直接写成数字即可
  3. 如果是复合属性,那么要使用小驼峰命名法
const styles = {width: '300px',height: '100px',background: 'red'}const root = ReactDOM.createRoot(document.querySelector("#root"));root.render((<div><div style={styles}>我是测试行内样式的div</div>{/* style的值必须是一个对象:第一个{}表示的是插值表达式的边界, 第二个{}表示对象的边界 */}<div style={{ width: '300px', height: '100px', border: '1px solid blue' }}>我是测试行内样式的div</div>{/* 如果样式的单位是px,那么px可以省略,值直接写成数字即可 */}<div style={{ width: 300, height: 100,fontSize:30, border: '1px solid red' }}>我是测试行内样式的div</div>{/* 如果是复合属性,那么要使用小驼峰命名法 */}<div style={{ width: 300, height: 100,fontSize:30,backgroundColor:'skyblue', border: '1px solid red' }}>我是测试行内样式的div</div></div>))

2-5-2. className通过类名定义样式

必须使用className指定类名,不能使用class

  1. className :可以直接赋值字符串
  2. className: 多个样式类名中间用空格隔开,可以同时生效
  3. 如果样式是一个数组,使用 join(’ '),进行赋值渲染
const root = ReactDOM.createRoot(document.querySelector("#root"));const classStr = 'c1 bg1'const classArr = ['c1', 'bg1', 'f1']root.render((<div><div className="c1" >c1 class 测试</div><div className={"bg1"}>bg1 class 测试</div><div className={"f1"}>f1 class 测试</div><div className={classStr}>c1 bg1 同时,字符串写法</div><div className="c1 bg1">c1 bg1 同时,字符串写法</div><hr />{/* 插值语法渲染数组,如果是渲染内容,那么挨个遍历渲染 如果数组是渲染className , 那数组中的每一个元素中间用 , 隔开了*/}<div className="c1 bg1 f1">c1 bg1 f1 样式同时出现 数组写法</div><div className={classArr}>c1 bg1 f1 样式同时出现 数组写法,直接写不好使,中间有,</div><div className={classArr.join(' ')}>c1 bg1 f1 样式同时出现 数组写法</div><div>{classArr}</div></div>))

2-6- 列表渲染

利用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((//         <div>{arr}</div>//     ))// }{// 2. 插值表达式渲染的数组,数组中的元素可以是react元素// const arr = [<p>1</p>, <p>2</p>, <p>3</p>]// root.render((//     <div>{arr}</div>// ))}{// 3. 将普通数组,转化为react元素的数组,然后渲染// 3-1. for循环进行转化// let arr = [1,2,3,4]// let elementArr = []// for(let i = 0;i<arr.length;i++){//     elementArr[i] = <p>{arr[i]}</p>// }// root.render((//     <div>{elementArr}</div>// ))// 3-2. forEach转化// let arr = [1,2,3,4]// arr.forEach((item,index)=>{//     arr[index] = <p>{item}</p>// })  // root.render((//     <div>{arr}</div>// ))// 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 <p>{item}</p>// })// console.log(newArr)// root.render((//     <div>{newArr}</div>// ))// 下面代码与上面 7 行等价// root.render((//     <div>{arr.map(item => <p>{item}</p>)}</div>// ))// 4. 关于控制台报的 key的错误,解决方案// 当做列表渲染的时候,由于diff 算法的原因,需要给每一个遍历的元素标签加上唯一的 key属性值// key属性值不能重复. // 所以我们一般使用数据的 id [唯一标识作为key值]// key属性仅用作diff算法,渲染完成后,不会出现在真实dom标签上// key 可以使用 索引作为key值,但是不推荐。// root.render((//     <div>{arr.map(item => <p key={item}>{item}</p>)}</div>// ))// 5. 使用map渲染元素是对象的数组const users = [{id: 1,username: 'atguigu',pwd: 'skdjflskfds'},{id: 2,username: 'yuonly',pwd: '243124'},{id: 3,username: 'houhou',pwd: '12312321'}]root.render((<div><ul>{/*{users.map(u => {return (<li key={u.id}><p>id:{u.id}</p><p>username:{u.username}</p><p>pwd:{u.pwd}</p></li>)})}以上代码还可以省略 {} 和return 见下面*/}{users.map(u => (<li key={u.id}><p>id:{u.id}</p><p>username:{u.username}</p><p>pwd:{u.pwd}</p></li>))}</ul></div>))}

2-6. jsx中的事件

2-6-1. 原生dom事件

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,说明函数的调用者是 windowconsole.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事件:是输入内容有变化就触发. 事件类型是 InputEventinput2.oninput = function(e){console.log('oninput e: ',e)console.log('pwd value: ', e.target.value)}

2-6-2. jsx中的事件

  • 原生的绑定事件: 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()`
      
<!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">/*** 原生的绑定事件: 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事件*/function btnFn(){console.log('this: ',this)console.log('btnfn')}const root = ReactDOM.createRoot(document.querySelector("#root"));root.render((<div><a href="http://baidu.com" onClick={(e)=>{console.log('a click')// 阻止默认行为:e.preventDefault(); // 支持,可以阻止默认行为// return false; 不支持,阻止不了默认行为console.log('e',e)//this 值为undefined,说明两件事// 1. react中事件回调的this 是指向window的。// 2. react中默认使用的是严格模式,所以, this不是window 而是 undefinedconsole.log('this: ',this) // this 值为undefined}} >百度</a><br/><button onClick={btnFn}>点我1</button> <br/><input type="text" name="" id="" onChange={(e)=>{// react中的onChange事件,不是失去焦点的时候触发的。输入就触发。console.log('input value: ',e.target.value)console.log('onchange e: ', e) // InputEvent,是原生的 oninput的事件对象}}/></div>))
</script>
</html>

2-7. jsx综合案例练习

需求:实现评论列表功能

  • 如果有评论数据,就展示列表结构 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 ? <h1>暂无评论</h1> : (<ul>{list.map(item=>(<li key={item.id}><a href="http://baidu.com" onClick={e=>e.preventDefault()}><h3 style={{fontSize:25}}>{item.name}</h3><p className="c1" onClick={()=>fn(item)}>{item.content}</p></a></li>))}</ul>)
)
root.render((<div>{vdom}</div>
))

2-8. jsx 中的三种注释方式

  1. 多行注释 {/* */}

  2. 单行注释

    {//
    }
    
  3. 属性注释 /* */

const root = ReactDOM.createRoot(document.querySelector("#root"));
root.render((<div>{/* 1. 多行注释 【推荐】*/}{/*中间都是注释的内容*/}{/* 2. 单行注释 [一般不用]*/}{// 单行注释代码// 仅我可见as}{/*3. 属性注释*/}<button className="c1" /*id="box"*/  >按钮</button></div>
))

2-9. 文档碎片

React.Fragment

react元素必须有唯一的根节点,但会导致多一层html标签结构,如果你希望直接渲染子几点到页面中,就可以使用文档碎片。将多个子节点放入文档碎片中。让文档碎片充当唯一的根节点。在渲染成真实dom时,文档碎片不被渲染,只渲染它内部的内容

const root = ReactDOM.createRoot(document.querySelector("#root"));
const { Fragment } = React
const vdom = (// 写法一// <React.Fragment>//     <div>//         我是div1//     </div>//     <div>//         我是div2//     </div>// </React.Fragment>// 写法二:// <Fragment>//     <div>//         我是div1//     </div>//     <div>//         我是div2//     </div>// </Fragment>// 写法三:<><div>我是div1</div><div>我是div2</div></>
)
root.render(vdom)

3. react脚手架

create-react-app

作用:快速构建一个工程化的react开发环境

3-1. 使用脚手架搭建项目

3-1-1. 全局安装脚手架

# 方式一: 使用npm安装
npm i create-react-app -g  # 就会多一个命令  create-react-app 的命令
# 方式二: 使用yarn安装 [了解]
yarn add create-react-app global

3-1-2. 使用脚手架创建react项目

# npm [课堂使用]
create-react-app 项目名 # 项目名不要叫 react, 不要有中文# yarn [了解]
yarn create react-app 项目名

3-1-3. 运行项目

# npm
cd 项目名  # 进入项目的根目录
npm start# yarn
cd 项目名
yarn start

3-1-4. 常见yarn命令

# 初始化项目
yarn init
# 安装
yarn add 包名
yarn add 包名 global
# 删除包
yarn remove 包名
yarn reomve global 包名
# 运行
yarn start
# 通过git clone 项目后,是没有 node_modules目录的,安装依赖包
yarn

3-1-5. yarn 项目和 npm项目如何区分?

yarn 项目的版本锁文件: yarn.lock

npm 项目的版本锁文件: package-lock.json

3-2. 脚手架项目目录分析

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                                    项目说明文件

3-3. 脚手架目录精简

public目录只保留:favicon.ico、index.html

src目录: App.js[根组件]、index.js 【js入口文件】

  • public/index.html
<!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" /><metaname="description"content="Web site created using create-react-app"/><title>React App</title></head><body><div id="root"></div></body>
</html>
  • src/App.js
export default function App(){return (<div>App12312312</div>)
}
  • src/index.js
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/>)

3-4. react18 和 17 版本写法区别

// react 17 语法//import React from 'react'
// 差别一:ReactDOM 引入的包名不一样
//import ReactDOM from 'react-dom'//import App from './App'
// 差别二:无序创建root 使用ReactDOM直接render
//ReactDOM.render(<App/>, 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/>)

4. 组件

什么是组件?

组成页面整体的部件,称为组件。

一个页面:由若干的组件组成

具有独立html结构、css样式、js逻辑、一些静态资源

react中的组件有两种:

  1. 类组件:react16.4版本以前,开发主要使用类组件
  2. 函数组件:react16.4版本以后,主要使用函数组件

组件开发优势:

  1. 方便复用
  2. 多人协作开发

4-1. 类组件

一、类组件定义:

  1. 类组件使用class进行声明定义

  2. 类组件要继承 React.Component

  3. 类组件的类名就是组件名

  4. 类组件类名首字母必须大写

  5. 类组件,必须有render方法

  6. render方法执行后,必须要有返回值,返回值是什么,调用组件后渲染的内容就是什么。

    99.999% 返回值都是一个react元素

二、类组件的调用

  1. 组件使用jsx语法进行调用

    1-1.单标签:<组件名 />

    1-2.对标签:<组件名></组件名>

  2. 类组件被调用,是如何执行的

  3. 当遇到 时,首先会实例化 该组件 new App()

  4. 使用实例化出来的对象,调用 render方法

  5. 将render方法 return 的内容,替换掉 调用标签

  • src/index.js
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/>)
  • src/App.jsx
import React,{Component} from 'react'
export default class App extends Component{render(){console.log(this)return (<div>App - class</div>)}
}

4-2. 函数组件

一、函数组件的定义

  1. 函数组件就是一个函数
  2. 函数名就是组件名
  3. 函数名首字母必须大写【因为组件在调用的时候,是使用jsx语法调用的】
  4. 函数的返回值,就是组件渲染的结果

二、函数组件的调用

  1. <组件名/> 调用该组件,就是执行组件函数
  2. 函数的返回值,就是渲染的结果,替换掉调用标签
  • src/index.js
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/>)
  • src/App.jsx
function App(){return (<div>App - function</div>)
}
export default App

4-3. 函数组件和类组件的区别

  1. 调用后执行的方式不同

    1. 类组件:实例化类之后,执行render函数
    2. 函数组件:直接调用执行函数
  2. this指向不同
    1. 类组件有this: this指向当前组件的实例对象
    2. 函数组件this: 没有this,this是undefined
  3. 是否有自身的状态数据:
    1. 类组件: 有自身的状态数据 state
    2. 函数组件:本身没有状态数据 ,后期可以通过 hook函数具有状态数据
  4. 是否有生命周期:
    1. 类组件:有生命周期
    2. 函数组件:本身没有生命周期,后期可以通过hook函数模拟生命周期

4-4. 类组件的状态数据

  1. 创建状态数据

    1. 创建类的属性 state: 在构造函数中创建
    constructor(){// 因为App继承了 Component,所以首先要调用父类的构造函数super()// 定义自身的属性// state属性是特殊的,状态数据的属性,值一般是一个对象,对象中的属性就是状态数据this.state = {count:10,msg:'atguigu'}// 构造函数,都是类的实例调用的,所以此处的this永远指向组件的实例对象
    }
    
  2. 读取:

    1. 直接读取: {this.state.count}
    2. 解构之后读取:
    let {count,msg} = this.state
    {count}
    {msg}
    
  3. 改变状态

    前提:事件的回调函数中的this必须要指向 当前组件的实例对象[ 箭头函数 ]

    1. 直接赋值: this.state.count += 1

      问题:可以改变状态count的值,但是不能触发render方法的重新调用,页面看不到最新的结果

    2. 通过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.statereturn (<div> <h3>class App</h3><p>count: {this.state.count}</p><p>msg: {msg}</p><p><button onClick={function(){console.log('this: ', this) // undefined  说明是window调用的}}>事件回调函数是window调用的</button></p><p><button onClick={()=>{console.log('this: ', this)}}>箭头函数this指向当前组件实例</button></p><p><button onClick={()=>{// 直接给state赋值,可以改变状态数据,但是不会触发页面重新渲染// 所以,看不到改变后的结果this.state.count += 1console.log(this.state.count)}}>count+1 数据改变了但不重新渲染</button></p><p><button onClick={()=>{// 改变状态数据,并触发render重新渲染.可以看到更新后的结果this.setState({count:this.state.count + 1})}}>count+1 数据变,页面也变</button></p></div>)}
}

4-5. 类组件状态定义的三种方式

  1. constructor 构造函数中定义
  2. state直接赋值定义 【相当于也是在constructor中执行的,只不过执行时机比constructor要早】
  3. 混合定义
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 (<div><p>count:{this.state.count}</p>{/* <p>msg:{this.state.msg}</p> */}<p><button onClick={()=>{this.setState({count:this.state.count + 1})}}>count+1</button></p></div>)}
}

4-6. 父子组件

组件的嵌套:在一个组件中调用另一个组件,就形成了组件的嵌套

组件嵌套会产生父子组件的概念:

  1. 外部组件是内部组件的父组件
  2. 内部组件是外部组件的子组件
  • App.jsx
 import React, { Component } from 'react'// 导入Child组件
import Child from './components/Child'
/*** 在一个组件中,调用另一组件,组件就形成了嵌套关系* 外部组件称为内部组件的父组件* 内部组件是外部组件的子组件*/
export default class App extends Component {render() {return (<div><h3>App组件</h3><hr /><Child/></div>)}
}
  • src/components/Child.jsx
import React, { Component } from 'react'export default class Child extends Component {render() {return (<div><h4>Child</h4></div>)}
}

4-7. 组件通信

4-7-1. 父传子-props

4-7-1. 类组件

4-7-1-1. props-静态数据

props通信:

  1. 数据的传递:在父组件中通过给子组件绑定属性的方式传递数据
  2. 数据接收:在子组件中通过this.props 特殊属性,接收到父组件传递过来的数据
  • 父组件: App.jsx
import Child from './components/Child'
export default class App extends Component {render() {return (<div><h3>App组件</h3><hr />{/*通过属性传递数据给子组件*/}<Child username='atguigu' age={10}/></div>)}
}
  • 子组件:src/components/Child.jsx
import React, { Component } from 'react'
export default class Child extends Component {render() {console.log('child: ',this.props)let {username, age} = this.propsreturn (<div><h4>Child</h4><p>props username: {this.props.username} - {username}</p><p>props age: {this.props.age}- {age}</p></div>)}
}
4-7-1-2. props- 状态数据

父组件向子组件传递状态数据

  1. 传递方式:调用子组件的标签,通过属性传递

    1. 逐个属性单独赋值传递
    2. 通过…扩展运算批量传递
  2. 接收方式: 子组件中通过 this.props特殊属性接收
  • App.jsx
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.statereturn (<div><h3>App组件</h3><hr /><h4>子组件中接收数据,取决于属性名,而不是变量名</h4><Child username='atguigu' age={10} num={num} message={message}/><hr /><Child username='尚硅谷' age={100} num={num} message={message}/><hr /><h4>通过扩展运算... 批量传递状态数据</h4><Child username='批量传递数据' age={300} {...this.state}  /></div>)}
}
  • src/components/Child.jsx
import React, { Component } from 'react'
export default class Child extends Component {render() {console.log('child: ',this.props)let {username, age, num, message} = this.propsreturn (<div><h4>Child</h4><p>props username: {this.props.username} - {username}</p><p>props age: {this.props.age}- {age}</p><p>App state count: {num}</p><p>App state msg: {message}</p></div>)}
}

4-7-2. 函数组件

函数组件接收外部数据,是通过函数参数接收。一般直接在参数位置进行解构

  • Child.jsx
/*** 函数组件如何接收外部数据?* @ props: 函数组件第一个参数,就是外部数据参数直接结构:推荐写法* @returns */
export default function Child({num, message, age}){return (<div><h3>Child 函数组件</h3><p>app num: {num}</p><p>app message: {message}</p><p>app age: {age}</p></div>)
}// 如何获取和使用外部数据【不推荐】
// export default function Child(props){
//     console.log('props: ',props)
//     let {num, message, age} = props
//     return (
//         <div>
//             <h3>Child 函数组件</h3>
//             <p>app num: {props.num}</p>
//             <p>app message: {props.message}</p>
//             <hr />
//             <p>app num: {num}</p>
//             <p>app age: {age}</p>
//             <p>app message: {message}</p>
//         </div>
//     )
// }

4-7-2. 子传父-props

父组件通过props传递一个方法给子组件

步骤:

  1. 在父组件中定义一个方法

  2. 将该方法通过标签属性的方式,传递给子组件

    1. 改变this指向之后,在传递,确保函数内部的this指向父组件的实例对象

      因为,接收到数据之后,我们一般会改变父组件的状态

  3. 子组件通过props接收

  4. 子组件中调用该方法,并将要传递的数据以参数的形式给父组件

  • App.jsx
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.statereturn (<div><h2>App父组件</h2>{/* 2. 通过标签属性,传递方法 */}<Child getSonData={this.getSonData.bind(this)} age={10} num={num} message={message}/></div>)}
}
  • src/components/Child.jsx [子组件是函数组件]
/*** 函数组件如何接收外部数据?* @ props: 函数组件第一个参数,就是外部数据* @returns */
// 3. 在子组件中通过props接收从父组件传递过来的 getSonData 方法
export default function Child({ num, message, age, getSonData }) {return (<div><h3>Child 函数组件</h3><p>app num: {num}</p><p>app message: {message}</p><p>app age: {age}</p><p><button onClick={()=>{getSonData(20000)}}>点击传递数据给父组件</button></p></div>)
}
  • src/components/Child.jsx [子组件是类组件]
import {Component} from 'react'
export default class Child extends Component {render(){let {num, message, age, getSonData} = this.propsreturn (<div><h3>Child 类组件</h3><p>app num: {num}</p><p>app message: {message}</p><p>app age: {age}</p><p><button onClick={()=>{getSonData(20000)}}>点击传递数据给父组件</button></p></div>)}
}

4-7-3. props数据是只读的不能修改

外部传入的数据props是只读的,在子组件中不可以直接赋值修改

如果想改,需要到数据的来源,父组件中修改。需要用到 子传父的知识点

  • App.jsx
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.statereturn (<div><h3>App</h3>{/* 2. 改变this指向后,将方法给子组件 */}<Child count={count} setCount={this.setCount.bind(this)}/></div>)}
}
  • src/components/Child.jsx
import React, { Component } from 'react'export default class Child extends Component {render() {let {setCount} = this.propsreturn (<div><h4>Child</h4><p>app count props: {this.props.count}</p><p><button onClick={()=>{this.props.count += 100  //  Cannot assign to read only property 'count' of object '#<Object>// 不能给只读属性count 赋值// props数据是只读的,只能读,不能改}}>改变props count</button></p><p><button onClick={()=>{setCount(5)}}>改父组件的数据</button></p></div>)}
}

4-7-4. props.children

props.children : 可以获取到对标签调用组件时,对标签中间的内容

这也是单标签和对标签调用组件的差别

  1. <组件名 />
  2. <组件名>内容会被props.children接收到</组件名>
  • App.jsx
import React, { Component } from 'react'
import Button from './components/Button'
export default class App extends Component {render() {return (<div><h3>App</h3><Button username='atguigu'>保存</Button><Button>删除</Button></div>)}
}
  • src/components/Button.jsx
import React from 'react'export default function Button(props) {console.log('props: ',props)return (<button>{props.children}</button>)
}

4-7-5. props校验及默认值

可以使用prop-types 包,来对外部传入的数据类型和默认值进行限定

  1. 类组件:

    1. static propTypes: 限定 类型和是否必填
    2. static defaultProps: 限定默认值
  2. 函数组件:
    1. 函数名.propTypes: 限定 类型和是否必填
    2. 函数名.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 (<div><h3>类组件props测试</h3><p>name: {this.props.name}</p><p>age: {this.props.age}</p></div>)}
}
  • 函数组件
import React from 'react'
import PropTypes from 'prop-types'
export default function PropsFun({name,age}) {return (<div><h3>函数组件props限定</h3><p>name: {name}</p><p>age: {age}</p></div>)
}
//校验规则
PropsFun.propTypes = {name:PropTypes.string.isRequired,age:PropTypes.number
}
// 默认值
PropsFun.defaultProps = {age:900
}

4-8. 类组件中事件回调this指向问题

事件回调的函数,都是window调用的

有三种方案,使得事件回调的this指向当前组件的实例

  1. 包裹箭头函数【推荐】

    1. 可以传参
    2. 方法是定义在原型上的
  2. bind【推荐】
    1. 可以传参
    2. 方法是定义在原型上的
    3. 父子组件通信时,传递给子组件的方法推荐用bind
  3. 直接定义成箭头函数【不推荐】
import React, { Component } from 'react'export default class App extends Component {state = {count:1}click1(num){// 普通方法,this只跟调用者有关。// click1 作为单击事件回调的函数,调用者是window,因为是严格模式,所以是undefinedconsole.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 (<div><h3>App</h3><p>count: {this.state.count}</p><p><button onClick={this.click1}>有问题的this</button></p><p><button onClick={()=>{// 箭头函数没有自己的this,使用的是上级作用域的this,render中的this是向前实例this.click2(5)}}>包裹箭头函数的this - 1</button></p>{/*包裹箭头函数:优点: 1,函数是定义在类的原型对象上的。共用一个2. 可以传递参数*/}<p><button onClick={()=>this.click2(8)}>包裹箭头函数的this - 2 -简写形式</button></p>{/* bind会返回一个新函数,新函数的this已经被改变了,用的也是render中的this 优点:1. 函数是定义在类的原型对象上的。共用一个2. 可以传参3. 返回一个新函数,this指向已经改变了的。传递给子组件时,非常方便*/}<p><button onClick={this.click3.bind(this,10)}>bind处理的this</button></p>{/* click4 中的this 用的是constructor中的this. 缺点:1. 不能传参2. 箭头函数不是定义在原型对象上的,每一个实例都有一个自己的。性能也不好*/}<p><button onClick={this.click4}>直接定义箭头函数的this</button></p></div>)}
}

4-9. setState深入理解

this.setState 用来改变状态数据。是异步的改变

用法:

  1. 参数是一个对象:this.setState({count:1})

  2. 参数是一个回调函数:

    1. 回调函数:是异步的
    2. 回调函数的参数,是最新的改变后的状态数据
    3. 回调函数的返回值,是最新的状态数据
    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 (<div><p>count: {this.state.count}</p><p ><button onClick={() => {console.log('11111')// setState是异步执行的,所以结果每次只能+1this.setState({count: this.state.count + 1})this.setState({count: this.state.count + 1})this.setState({count: this.state.count + 1})this.setState({count: this.state.count + 1})console.log('22222')}}>count ++</button></p><p><button onClick={()=>{// setState 参数可以是一个回调函数,该回调函数是异步的// 回调函数的参数,就是当前组件的状态数据【最新的状态】// 回调函数的返回值,就是改变后的最新的状态值console.log('1111')this.setState((state)=>{return {count: state.count + 1}})this.setState((state)=>{return {count: state.count + 1}})this.setState((state)=>{return {count: state.count + 1}})console.log('2222')}}>setState 接收一个函数作为参数</button></p></div>)}
}

4-10. 开发时的组件目录结构

4-11. react css 样式

4-11-1. css 样式引入的位置

  1. 成熟的第三方库:

    1. 文件放置位置: 项目目录/public/css/xxxx.css
    2. 引入的方式:项目目录/public/index.html head中 通过link标签引入
  2. 自定义的全局样式:
    1. 文件放置位置: src/App.css
    2. 引入位置:src/App.jsx 中通过import 语法引入
  3. 组件独有样式:
    1. 文件放置位置:组件目录/css/xxx.css
    2. 引入位置:组件中使用 import 语法引入
  • 成熟第三方库

  • 自定义全局通用样式

  • 组件独有样式

4-11-2. css 模块化

css in js

将css文件样式转化为js模块来使用,好处是避免不同的组件的相同的样式名的冲突

确保各个组件样式的独立性

实现模块化步骤:

  1. 命名:css文件必须以 xxx.module.css 命名
  2. 引入: import styles from ‘./xxxx.module.css’
  3. 使用:styles.类名

4-12. 图片

4-12-1. 网络图片

直接将图片路径填到 img src属性即可

4-12-2. 本地图片

  1. require

    • 注意:静态资源的目录,必须放在src目录下面。
  2. import
import React, { Component } from 'react'
import img from './assets/images/2.webp'
export default class App extends Component {render() {return (<div><h3>图片</h3><h5>网络图片</h5><img width={500} src="https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/ed5b4c0f1853e63df778a7f6699830ea.jpg?thumb=1&w=1226&h=460&f=webp&q=90" alt="" /><h5>本地图片</h5><img width={600} src={require("./assets/images/1.webp")} alt="" /><br/><img width={700} src={img} alt="" /></div>)}
}

4-13. 练习

  1. 完成组件拆分
  2. 完成静态布局
  3. 首屏数据渲染
  4. 完成交互

4-13-1. 轮播图练习

  • Swipper.jsx
import React, { Component } from 'react'
import './css/index.css'
export default class Swipper extends Component {state = {index:4}render() {let {index} = this.statereturn (<div className="wrapper"><img src={require(`./images/${index}.webp`)} alt="" /><span onClick={()=>{index--;if(index <=0 ) index = 4;this.setState({index})}}> &lt; </span><span onClick={()=>{index++;if(index >=5 ) index = 1this.setState({index})}} className="right"> &gt; </span></div>)}
}
  • 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;
    }
    

4-13-2. tab切换练习

  • Tab.jsx
import React, { Component } from 'react'
import './index.css'
export default class Tab extends Component {state = {index:1}render() {let {index} = this.statereturn (<div><h3>Tab</h3><div className="box"><button onClick={()=>{this.setState({index:1})}} className={index === 1 ? 'active' : ''}>北京</button><button onClick={()=>{this.setState({index:2})}}  className={index === 2 ? 'active' : ''}>上海</button><button onClick={()=>{this.setState({index:3})}}  className={index === 3 ? 'active' : ''}>深圳</button><div className="show" style={{display:index===1 ? 'block':'none'}}>北京</div><div className="show" style={{display:index===2 ? 'block':'none'}}>上海</div><div className="show" style={{display:index===3 ? 'block':'none'}}>深圳</div></div></div>)}
}
  • index.css
.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;
}

4-14. 类组件的生命周期

4-14-1. 挂载阶段

执行顺序:constructor=> render => componentDidMount

constructor、componentDidMount 只执行一次,挂载后不在重复执行

render: 挂载阶段执行,更新也会执行

componentDidMount 一般会做如下操作

  1. 开启定时器
  2. 发送ajax请求
  3. 订阅消息
  4. 添加事件监听

存在父子组件嵌套时

  1. 先执行父组件的 constructor、render

  2. 执行子组件的 constructor、render、componentDidMount

  3. 执行父组件的 componentDidMount

4-14-2. 更新阶段

  1. 执行顺序: render ==> componentDidUpdate
  2. 什么方式可以触发更新?
    1. new props: props数据发生变化时
    2. setState: 重新设置状态数据时
    3. forceUpdate: 强制更新
  3. componentDidUpdate 【不常用】
    1. 发送ajax请求
    2. 将数据做本地化存储
  4. 对于组件来说,父组件更新了[render], 子组件无条件也会 render

4-14-2-1. shouldComponentUpdate【了解】

  1. 问题:类组件当外部数据重新设置,即便没有变化也会重新渲染,当状态数据重新设置,即便没有变化,也会重新渲染

  2. 作用:可以控制组件的render是否执行。可以实现:当组件中的props和state数据都没有变化的时候,不重新渲染

  3. should 应该 component 组件 update更新: 组件应该更新么?

    1. nextProps: 即将要更新到的props值
    2. nextState:即将要更新到的 state值
    3. 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,重新renderreturn true}}for(let attr in nextState){if(nextState[attr] !== this.state[attr]){return true}}return false
}

4-14-2-2. PureComponent 纯组件

作用: 当组件中的props和state数据都没有变化的时候,组件不重新render

4-14-3. 卸载阶段

componentWillUnmount : 组件即将卸载前执行

  1. 清除定时器
  2. 取消订阅
  3. 解绑事件监听

4-15. ref

ref作用:在react组件中可以通过绑定ref,来获取原生的dom元素

  1. ref=‘字符串’
  2. ref=回调函数
  3. ref=React.createRef()
import React, { Component, createRef } from 'react'export default class App extends Component {pwdRef = createRef()render() {return (<><div>App</div>{/* 方式一[不推荐]  ref=字符串 */}<input type="text" name="" id="" ref='username'/><br />{/* 方式二[不推荐]:ref=回调函数 弊端:写法太麻烦 */}<input type="text" name="" id="" ref={(el)=>{console.log('el', el)this.inputRef = el}}/>{/* 方式三[推荐]:React.createRef() */}<input type="text" name="" ref={this.pwdRef} id="" /><p><button onClick={()=>{// 方式一[不推荐]:当前组件实例对象身上会多一个 refs属性console.log(this.refs.username, this.refs.username.value)// 方式二: console.log(this.inputRef, this.inputRef.value)// 方式三:console.log('createRef: ',this.pwdRef.current, this.pwdRef.current.value)}}>获取原生dom元素</button></p></>)}
}

4-15-1. 受控组件

受控组件、非受控组件,说的都是表单元素

受控组件:表单元素的值,受到组件状态[state]的控制

  1. 给表单元素绑定 value,值是 状态数据state1. 绑定`onChange`回调函数,回调函数内部给state重新赋值1. 获取受控组件的表单数据:读取状态数据state即可。(表单数据和state已经<font color='red'>双向数据绑定</font>了)
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.valueif(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.statereturn (<div><form action="" onSubmit={this.submitHandler.bind(this)}><p>姓名:<input type="text" name="username" value={username} onChange={this.changeHandler.bind(this)} /></p><p>密码:<input type="text" name="pwd" value={pwd} onChange={this.changeHandler.bind(this)} /></p><p>性别:<input type="radio" name="sex" value="1" checked={sex === "1" ? true : false} onChange={this.changeHandler.bind(this)} /> 男<input type="radio" name="sex" value="0" checked={sex === "0" ? true : false} onChange={this.changeHandler.bind(this)} /> 女</p><p>城市:<select name="city" value={city} onChange={this.changeHandler.bind(this)}><option value="1">北京</option><option value="2">石家庄</option><option value="3">保定</option></select></p><p>爱好:<input type="checkbox" name="hobby" value="1" checked={hobby.includes("1")} onChange={this.changeHandler.bind(this)} /> 台球<input type="checkbox" name="hobby" value="2" checked={hobby.includes("2")} onChange={this.changeHandler.bind(this)} /> 篮球<input type="checkbox" name="hobby" value="3" checked={hobby.includes("3")} onChange={this.changeHandler.bind(this)} /> 足球<input type="checkbox" name="hobby" value="4" checked={hobby.includes("4")} onChange={this.changeHandler.bind(this)} /> 滑雪</p><p><button>提交</button></p></form></div>)}
}

4-15-2. 非受控组件

非受控组件:只实现了单项数据绑定[ 从状态数据到视图显示 ]

  1. 绑定数据渲染:defaultValue、defaultChecked
  2. 获取用户数据:
    1. 通过绑定ref的方式,通过真实dom元素,获取用户最新输入
    2. 就涉及到数组的常用方法 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 (<div><form action="" onSubmit={this.submitHandler.bind(this)}><p>姓名:<input type="text" defaultValue={username} name="username"  ref={this.usernameRef}/></p><p>密码:<input type="text" defaultValue={pwd} name="pwd"  ref={this.pwdRef}/></p><p>性别:<input type="radio" name="sex" value="1"  defaultChecked={sex==="1"} ref={this.sexRef[0]}/> 男<input type="radio" name="sex" value="0"  defaultChecked={sex==="0"} ref={this.sexRef[1]}/> 女</p><p>城市:<select name="city" defaultValue={city} ref={this.cityRef}><option value="1">北京</option><option value="2">石家庄</option><option value="3">保定</option></select></p><p>爱好:<input type="checkbox" name="hobby" value="1" ref={this.hobbyRef[0]}  defaultChecked={hobby.includes("1")}/> 台球<input type="checkbox" name="hobby" value="2" ref={this.hobbyRef[1]}  defaultChecked={hobby.includes("2")}/> 篮球<input type="checkbox" name="hobby" value="3" ref={this.hobbyRef[2]}  defaultChecked={hobby.includes("3")}/> 足球<input type="checkbox" name="hobby" value="4" ref={this.hobbyRef[3]}  defaultChecked={hobby.includes("4")}/> 滑雪</p><p><button>提交</button></p></form></div>)}
}

5. 组件化开发思路

编程思维:

组件化开发的开发思路:

  1. 拆分组件

  2. 组件化静态页面布局

  3. 首屏数据渲染

    3-1. 需要什么样的状态数据?【数据结构】

    3-2. 状态数据定义在哪个组件身上合适?

    1. 如果该数据只有一个组件使用,那么就放在这个组件身上

    2. 如果该数据需要多个组件使用,那么就放在他们共同的父级组件身上[状态提升]

      为什么要状态提升?

      因为多个组件需要同一个数据,就涉及到组件间通信,父子组件传递数据是最方便的

  4. 完成用户交互功能

    1. 用语言描述你要做什么
    2. 给描述的事情分步骤
    3. 给步骤罗列技术点
    4. 综合考量使用什么技术实现

5-1. 组件拆分

5-2. 静态布局

5-2-1. TodoList组件布局

  1. 创建对应组件的目录及文件
  2. 将整体html结构和整体的css,都拷贝到TodoList组件中,完成页面的正常显示
  3. 修改class ==> className

5-2-2. 将TodoList组件中的结构和样式拆分到各个组件

5-3. 首屏数据渲染

渲染列表:必须是数组。列表中的数据信息比较多,所以数组的每一项都得是一个对象

根据静态页面效果,分析数据结构如下:

todos = [{id:1title:'吃饭'isDone:true},{id:2,title:'睡觉',isDone:false}
]

状态提升

5-4. Footer-总条数-已完成数

let {todos} = this.props
let total = todos.length;
let doneNum = todos.reduce((pre, cur)=>pre + cur.isDone,0)

5-5. 添加代办事项

  1. 语言描述要做什么【干什么->效果】:

    在头部文本框输入内容,按回车后,可以渲染到列表中

  2. 拆分步骤:

    1. 文本框输入内容:
    2. 按回车
    3. 渲染到列表
  3. 根据步骤罗列技术点

    1. 获取文本框输入内容

      1. 非受控组件 ref:
      2. e.target:当时间源是文本框的时候,可以有限考虑使用
      3. 受控组件
    2. 按回车:
      1. 绑定键盘事件: onKeyUp、onKeyDown
      2. 判断按的是不是回车: keycode === 13 或 code == ‘Enter’
    3. 输入的内容渲染到列表:
      1. 子传父: 数据在哪里,参数数据的方法就要定义在哪里

        1. 父组件中定义方法
        2. 方法通过属性传递给子组件
        3. 子组件调用该方法,传递数据
      2. 创建一个新的对象 加入到 todos数组中,重置状态即可
  • Header.jsx

    1. 绑定keyup时间
    2. 获取文本框输入内容 e.target.value
    3. 调用父组件方法 传递title
    4. 清空文本框
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方法,传递titlethis.props.addTodo(title)// 3. 清空文本框e.target.value = ''}}render() {return (<div className="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.keyupHandler.bind(this)} />{/* <input type="text" placeholder="请输入你的任务名称,按回车键确认" onKeyUp={this.keyupHandler.bind(this)} ref={this.titleRef} /> */}{/* <input type="text" placeholder="请输入你的任务名称,按回车键确认"value={this.state.title}onKeyUp={this.keyupHandler.bind(this)}onChange={this.changeHandler.bind(this)}/> */}</div>)}
}
  • TodoList.jsx
  1. 定义addTodo方法

    1. 获取header传递过来的title
    2. 拼接 todo对象
      1. 生成id : nanoid

        1. 安装:npm i nanoid
        2. 导入: import {nanoid} from 'nanoid'
        3. 生成: nanoid()
    3. 添加并重置状态todos
  2. 传递给Header组件
// 添加todo
addTodo(title){// 1. 获取子组件header中的输入内容titleconsole.log('TodoList : ',title)// 2. 拼一个新的todo对象const todo = {id:nanoid(),title,isDone:false}// 3. 加入数组,并执行setState触发更新this.setState({todos:[todo, ...this.state.todos]})
}

5-6. 删除todo

  1. 描述[干了什么-》效果]:点击删除按钮,删除当前事项
  2. 步骤:
    1. 点击按钮
    2. 删除该条记录
  3. 技术:
    1. 点击按钮

      1. 绑定单击事件: onClick
    2. 删除该条记录
      1. 获取当前记录id
      2. 将id传递到 todos数据所在组件[TodoList组件]
      3. 在TodoList组件中定义 删除方法,根据id删除
      4. 重置状态数据,刷新列表

注意:

  1. 方法定义在TodoList组件中,因为数据在哪儿,操作数据的方法就在哪儿
  2. 方法要从爷爷组件【TodoList】,传递给孙子组件Item,List做为桥梁
  • TodoList.jsx

    定义 deleteById方法

    1. 接收到item组件传递的 id
    2. 根据id 过滤todos数据
    3. 重置状态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})
}
  • List.jsx

接收TodoList传递的 deleteById 方法,直接传递给Item组件

render() {let {todos,deleteById} = this.propsreturn (<ul className="todo-main">{todos.map(todo=><Item todo={todo} deleteById={deleteById} key={todo.id}/>)}</ul>)
}
  • Item.jsx
  1. 接收deleteById方法

  2. 调用并传递id参数

render() {let {todo,deleteById} = this.propsreturn (<li><label><input type="checkbox" checked={todo.isDone} onChange={()=>{}}/><span className={todo.isDone ? 'done' : ''}>{todo.title}</span></label><button className="btn btn-danger" onClick={()=>deleteById(todo.id)}>删除</button></li>)
}

5-7. 每条复选框完成已完成切换

  1. 描述功能:点击复选框,进行完成和未完成切换
  2. 步骤:
    1. 点击复选框
    2. 切换该条记录的完成未完成状态
  3. 技术点:
    1. 点击复选框:

      1. 定义onChange事件
    2. 切换该条记录的完成未完成状态
      1. 获取该条记录id
      2. 将变更切换的方法[checkedOne]定义在 TodoList组件身上
      3. 将该方法传递给item,执行,并传递id,还要传isDone最新的值
      4. 在方法 checkedOne 中
        1. 获取id,获取最新的checked值
        2. 遍历修改数组中对应id 的 isDone值为checked
        3. 重置状态todos
  • item.jsx

{/*
方式一
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo.id,!todo.isDone)}/>
*/}// 方式二:
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo)}/>
  • TodoList.jsx
// 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]})
}
  • 方式二原理图

5-8. checkAll

全选反选功能实现

  1. 获取最新的checked 值
  2. todos数组中每一个todoisDone属性修改成 checked
  • TodoList.jsx
checkedAll(isDone){console.log('isDone: ', isDone)// 使用isDone 去修改todos数组中每一个todo 的isDone属性即可this.setState({todos:this.state.todos.map(todo=>{todo.isDone = isDonereturn todo})})// let newTodos = this.state.todos.map(todo=>{//     todo.isDone = isDone//     return todo// })// this.setState({//     todos:newTodos// })
}
  • Item.jsx
<input type="checkbox" checked={todo.isDone} onChange={()=>checkedOne(todo)}/>

5-9. 删除已完成

过滤掉 isDone 是true的值,保留下 isDone是false的数据

  • TodoList.jsx
deleteDone(){this.setState({todos:this.state.todos.filter(todo=>!todo.isDone)})
}
  • Item.jsx
<button className="btn btn-danger" onClick={deleteDone}>清除已完成任务</button>

5-10. 将todos数据做本地化存储

localStorage

  1. 存储: localStorage.setItem(key, value)
  2. 读取: localStorage.getItem(key)
  3. 移除:localStorage.removeItem(key)
  4. 清空:localStorage.clear()

需求:

将todos数组存储在 localStorage中:

  1. 存储: JSON.stringify(数组)
  2. 读取:JSON.parse(JSON格式字符串)

6. 高阶组件

HOC:H higher O Order C component

高阶函数:函数的参数是函数或者函数的返回值是函数

高阶组件:组件的参数是组件或组件的返回值是组件

高阶组件作用:可以实现类组件代码的复用

6-1. 高阶函数

/***  函数的参数是函数,或函数的返回值是函数,就是高阶函数*  *  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* */

6-2. 高阶组件

// 1.0
// function WithApp(Com){
//     return Com
// }import {Component} from 'react'
// 2.0
// function WithApp(Com){
//     return class  extends Component{
//         render(){
//             return <Com/>
//         }
//     }
// }// 3.0 属性代理
// function WithApp(Com){
//     return class  extends Component{
//         render(){
//             return <Com username="atguigu" age="10"/>
//         }
//     }
// }// 4.0 属性代理
// function WithApp(Com,props){
//     return class  extends Component{
//         render(){
//             return <Com {...props}/>
//         }
//     }
// }// 5.0 参数分阶段传递
// function WithApp(props){
//     return function (Com){
//         return class  extends Component{
//             render(){
//                 return <Com {...props}/>
//             }
//         }
//     }
// }// 6. 0 状态数据和props一起传递给子组件Com
// function WithApp(props){
//     return function (Com){
//         return class  extends Component{
//             state = {
//                 ...props,
//                 money:1000
//             }
//             render(){
//                 return <Com {...this.state}/>
//             }
//         }
//     }
// }// 7.0
function WithApp(mapStateToProps){return function (Com){return class  extends Component{state = {...mapStateToProps(),money:1000}render(){return <Com {...this.state}/>}}}
}export default WithApp

6-3. 属性代理实现代码复用

高阶组件的作用:就是为了实现类组件的逻辑的复用

  • hoc/WithMove.jsx
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 <WC {...this.state}/>}}
}
  • components/TianE.jsx
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'class TianE extends Component {render() {let {x,y} = this.propsreturn (<div style={{width:100,height:100,border:'1px solid red',position:'absolute',left:x,top:y}}>尚硅谷的女老师们!!!</div>)}
}export default WithMove(TianE)
  • components/HaMa.jsx
import React, { Component } from 'react'
import WithMove from '../hoc/WithMove'class HaMa extends Component {render() {let {x,y} = this.propsx += 110y += 110return (<div  style={{width:100,height:100,border:'1px solid green',position:'absolute',left:x,top:y}}>晶哥!!!</div>)}
}
export default WithMove(HaMa)
  • App.jsx
import React, { Component } from 'react'
import HaMa from './components/HaMa'
import TianE from './components/TianE'export default class App extends Component {render() {return (<div><TianE/><HaMa></HaMa></div>)}
}

7. Hook

7-1. useState

给函数组件创建状态数据的

语法:

let [状态, 设置状态函数] = useState(初始值)

7-1-1. setXxx 函数的两种用法

  1. setXxx(要赋的值):

  2. setXxx(回调函数):

    1. 回调函数的参数: 最新的变化后的状态数据
    2. 回调函数的返回值:要设置的最新的值

    应用:当使用方式一设置值不正确,发现无法获取到最新的数据时,可能是由于闭包导致的。所以,要考虑使用方式二

import React, {useEffect, useState} from 'react'export default function App() {let [msg, setMsg] = useState('atguigu')useEffect(()=>{// componentDidMountsetTimeout(()=>{// 方式一:// msg 只能取到初始值 atguigu, 并不能取到最新变化后的值// setMsg(msg + '_')// 方式二:// 参数是一个回调函数,回调函数的参数,是最新的变化后的状态数据// 回调函数的返回值,是设置的最新的状态数据的值setMsg((msg)=>{return msg + '_'})},2000)},[])return (<div><h3>msg: {msg}</h3><p><button onClick={()=>{setMsg(msg + '+')}}>msg + </button></p></div>)
}

7-2. useEffect

作用:模拟生命周期函数

  1. componentDidMount
  2. componentDidUpdate
  3. componentWillUnmount

注意:

  1. 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 ()=>{// 这里模拟的是 componentWillUnmountconsole.log('componentWillUnmount - 模拟组件销毁是触发')}
},[])

7-3. useRef

作用:创建ref对象,在函数组件中绑定获取真实dom元素

用法:

  1. 引入: import {useRef} from 'react'

  2. 创建:

    const inputRef = useRef()

  3. 绑定:

    <input type='text' ref={inputRef} />

  4. 获取:

    inputRef.current

7-3-1. 受控组件

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.valueif(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 (<div><div><form action="" onSubmit={submitHanlder}><p>username: <input type="text" name="" value={username} onChange={changeHandler(setUsername)}/></p><p>pwd: <input type="text" name="" value={pwd} onChange={changeHandler(setPwd)}/></p><p>性别:<input type="radio" name="sex"  value="1" checked={sex==="1"} onChange={changeHandler(setSex)}/> 男<input type="radio" name="sex"  value="0" checked={sex==="0"} onChange={changeHandler(setSex)}/> 女</p><p>city: <select name="" value={city} onChange={changeHandler(setCity)}><option value="1">北京</option><option value="2">上海</option><option value="3">深圳</option></select></p><p>爱好:<input type="checkbox" name="" value="1" checked={hobby.includes("1")} onChange={changeHandler(setHobby)}/> 台球<input type="checkbox" name="" value="2" checked={hobby.includes("2")} onChange={changeHandler(setHobby)}/> 滑冰<input type="checkbox" name="" value="3" checked={hobby.includes("3")} onChange={changeHandler(setHobby)}/> 潜水<input type="checkbox" name="" value="4" checked={hobby.includes("4")} onChange={changeHandler(setHobby)}/> 撑杆跳</p><p><button>提交</button></p></form></div></div>)
}

7-3-2. 非受控组件

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"])// 定义refconst 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 (<div><h3>App</h3><div ref={divRef}>div</div><p><button onClick={()=>{console.log('div dom: ', divRef.current)}}>获取真实Dom</button></p><div><form action="" onSubmit={submitHanlder}><p>username: <input type="text" name="" defaultValue={username} ref={usernameRef}/></p><p>pwd: <input type="text" name="" defaultValue={pwd} ref={pwdRef}/></p><p>性别:<input type="radio" name="sex" ref={sexRef[0]} value="1" defaultChecked={sex==="1"}/> 男<input type="radio" name="sex" ref={sexRef[1]} value="0" defaultChecked={sex==="0"}/> 女</p><p>city: <select name="" defaultValue={city} ref={cityRef}><option value="1">北京</option><option value="2">上海</option><option value="3">深圳</option></select></p><p>爱好:<input type="checkbox" name="" defaultChecked={hobby.includes("1")} value="1" ref={hobbyRef[0]}/> 台球<input type="checkbox" name="" defaultChecked={hobby.includes("2")} value="2" ref={hobbyRef[1]}/> 滑冰<input type="checkbox" name="" defaultChecked={hobby.includes("3")} value="3" ref={hobbyRef[2]}/> 潜水<input type="checkbox" name="" defaultChecked={hobby.includes("4")} value="4" ref={hobbyRef[3]}/> 撑杆跳</p><p><button>提交</button></p></form></div></div>)
}

7-4. Hook使用规则

  1. React钩子函数,不能在类组件中调用
  2. React Hooks必须在React函数组件或自定义React Hook函数中调用
  3. 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 (<div>App</div>)
}// 报错: 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 (
//             <div>App</div>
//         )
//     }
// }export default App

7-5. 自定义hook函数

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')
//     })
// }

7-6. context

祖先组件向后代组件传参

使用步骤:

  1. 创建并导出 context:export default React.createContext()

  2. 使用context.Provider组件包裹后代组件,并传递数据

    <context.Provider value={{数据}} >后代组件
    </context.Provider>
    
  3. 在后代组件中获取数据

    1. 引入context: import context from './context.js'

    2. 使用useContext获取值

      let {数据} = useContext(context)
      
  • context.js
import React from 'react'
// 创建一个context对象并导出
export default React.createContext()
  • App.jsx
import React from 'react'
import Father from './components/Father'
import context from './context'
export default function App() {return (<div style={{ width: 500, height: 500, border: '1px solid red' }}>{/* 使用context中的Provider组件包裹,后代组件,并传递数据value */}<context.Provider value={{username:'atguigu'}}><h3>App</h3><Father></Father></context.Provider></div>)
}
  • 后代组件中
import React, { useContext } from 'react'
import Son from './Son'
// 导入context对象
import context from '../context'
export default function Father() {let { username } = useContext(context)return (<div style={{ width: 300, height: 300, border: '1px solid blue' }}><h4>Father</h4><p>username: {username}</p><hr /><Son /></div>)
}

7-7. pubsub

作用:实现任意组件间的通信

原理:利用发布订阅原理

使用:

  1. 安装: npm i pubsub-js

  2. 导入:import PubSub from 'pubsub-js'

  3. Api

    1. 订阅消息: let 消息id = PubSub.subscribe(消息名, (消息名, 数据)=>{})
    2. 发布消息: PubSub.publish(消息名, 数据)
    3. 取消订阅:
      1. 取消自己的订阅: PubSub.unsubscribe(消息id)
      2. 取消该消息名的所有消息: PubSub.unsubscript(消息名)
      3. 取消所有消息:clearAllSubscriptions

8. ajax

  1. 安装axios
  2. 配置:
  3. 使用:
  • 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
  • 在组件中使用 App.jsx
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 (<div>App</div>)
}

9. 路由

10. redux

大型项目,组件间进行数据通信,数据的管理和维护比较麻烦。我们希望对数据进行统一的管理。使用redux

redux: 统一进行数据状态管理的库。

10-1. 三个核心概念:

10-1-1. store: 数据仓库

10-1-2. reducer[执行者]:专门操作仓库数据的函数

10-1-3. action: 提出修改数据的需求

10-2. redux基本使用

  1. 安装:npm install @reduxjs/toolkit

  2. 导入:import {createSlice} from '@reduxjs/toolkit'

  3. 创建 切片

// 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.actionslet res = addNum(1000) // 使用addNum actionCreator 创建一个action
console.log('res: ', res) // {type: 'count/addNum', payload: 1000}
  1. 创建仓库
// 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))

10-3. 添加商品

/***  使用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.1231532234547643id: 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}))

10-4. store模块化

src

|- store 整个redux数据目录

​ | |- slice 所有切片目录

| |- index.js 创建的store仓库入口文件

  • store/slice/goodsSlice
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.1231532234547643id: 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
  • store/index.js
import {configureStore} from '@reduxjs/toolkit'
import goods from './slice/goodsSlice'
// 2. 创建仓库
const store = configureStore({reducer: {goods}
})export default store
  • 调用使用: src/index.js
// 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
}))

11. redux小练习

需求:

点击按钮发送ajax请求,获取一句话,将一句话的长度累计到 redux状态数据

文件介绍:

  1. request/index.js: axios 统一配置文件【基础路径、响应拦截器简化返回数据】
  2. api/onSentence.js: 所有跟一句话相关的请求接口方法
  3. store: 数据仓库
  4. store/slice/countSlice:
    1. 定义状态数据num
    2. 使用createAsyncThunk 创建了异步的actionCreate
      1. 调用了api接口
    3. extraReducer中,添加了case,处理请求后的数据
  5. App.jsx
    1. 展示状态数据
    2. 点击按钮,触发异步的actionCreator方法
  6. src/index.js
    1. 导入Provider
    2. 包裹根组件,传递store数据

12-redux-todoslist-综合练习

todos数据来源于数据库,在前端使用redux进行管理。因此,需要保证远端服务器数据和redux中的数据是同步的。因此,在进行用户交互操作的时候, 如果改动了数据,需要修改远端数据库的数据,也要修改redux中管理的数据。

  1. 修改远端服务器数据:在异步的actionCreator中进行
  2. 修改redux中的数据:在addCase中进行

12-1. request和api的封装

  1. request/index.js
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
  1. 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')
}

12-2. 首屏数据渲染

获取服务器端 todoList数据,初始化redux中的状态数据。在组件中读取 redux中的todos数据,进行渲染

12-2-1. 创建store

  1. store/index.js
import { configureStore } from "@reduxjs/toolkit";
import todoList from './slice/todoSlice'
const store = configureStore({reducer:{todoList}
})
export default store
  1. store/slice/todoSlice.js

    1. 定义异步的actionCreator: 发送ajax请求,获取所有todos数据
    2. 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=>{// 初始化todosbuilder.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
  1. src/index.js

导入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>
)

12-2-2. 组件加载完之后,compomentDidMount中,发送ajax请求

  1. App.jsx 中
export default function App() {const dispatch = useDispatch()useEffect(()=>{// 初始化todosdispatch(asyncGetTodos())},[])return (<><TodoList /></>)
}
  1. List组件中,获取redux中的todos,并进行遍历渲染
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 (<ul className="todo-main">{todos.map(todo=><Item key={todo._id} todo={todo}/>)}</ul>)
}

13. 其他hook

13-1. useRef-React.forwardRef-useImpretiveHandle

useRef:

  1. 绑定dom元素,获取真实dom

  2. 可以单独模拟componentDidUpdate

  3. 给以给类组件绑定,获取类组件的实例对象

    1. 给函数组件绑定:

      4-1. 本身不支持

      4-2. 函数组件使用 React.forwardRef 包裹再导出

      ​ 4-2-1. 函数组件的第二个参数,可以收到父组件的ref对象

      ​ 4-2-2. 可以使用useImpretiveHandle,给父组件的ref对象的current属性,重新赋值,暴露操作的方法

  • App.jsx
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 + componentDidUpdateif (flagRef.current) {// didMountflagRef.current = falsereturn;}// didUpdate})// 类组件reflet classRef = useRef()// 函数组件reflet funRef = useRef('fun')return (<div><h3>类组件</h3><ClassTest ref={classRef} /><hr /><h3>函数组件</h3>{/* 函数组件本身不能绑定ref */}{/* 可以使用React.forwordRef处理函数组件后,进行ref的绑定 可以在父组件中操作子组件的真实dom元素*/}<FunTest ref={funRef} /><button onClick={() => {console.log(classRef.current) // ref可以绑定给类组件获取类组件的实例对象// console.log(funRef.current)funRef.current.changeColor()funRef.current.changeSize()}}>获取ref</button></div>)
}
  • ClassTest.jsx
import React, { Component } from 'react'export default class ClassTest extends Component {state = {msg:'类组件'}render() {return (<div>ClassTest</div>)}
}
  • FunTest.jsx
import React,{useImperativeHandle, useRef} from 'react'function FunTest(props,ref) {console.log('ref: ', ref)// 子组件自己的reflet selfRef = useRef()useImperativeHandle(ref, ()=>({changeColor:()=>{selfRef.current.style.color = 'blue'},changeSize:()=>{selfRef.current.style.fontSize = '100px'}}))return (<div ref={selfRef}>FunTest</div>)
}export default React.forwardRef(FunTest)

13-2. useReducer[了解]

模拟一个小型的 redux,做简易版的集中状态管理

  • App.jsx
import React from 'react'
import Test1 from './components/Test1'export default function App() {return (<div><Test1/></div>)
}
  • Test1.jsx
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} <br />msg: {state.msg} <br /><button onClick={() => dispatch({ type: 'decrement' })}>-</button><button onClick={() => dispatch({ type: 'increment' })}>+</button></>)
}

13-3. useCallback 【掌握】

避免组件更新,函数重新创建。

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 (<div><p>count: {count}</p><p><button onClick={handle}>count + </button></p></div>)
}

13-4. useMemo【掌握】

缓存一个函数运行的结果

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 <div><h4>{count}-{expensive}</h4>{val}<div><button onClick={() => setCount(count + 1)}>+c1</button><input value={val} onChange={event => setValue(event.target.value)}/></div></div>;
}

13-5. React.memo【掌握】

类似类组件的纯组件功能,当自身状态数据state和外部数据props没有变化的时候,不重新渲染

  • App.jsx
import React, { useState,Component } from 'react'
import Son from './components/Son'/*** * 函数组件自身状态数据没有变化,已经做过优化,只多渲染一次*/
export default class App extends Component {state = {count:100}render(){let {count} = this.stateconsole.log('app render')return (<div><p>count:{count}</p><Son count={count}/><p><button onClick={()=>{this.setState({count:111})}}>count+</button></p></div>)}
}
  • Son.jsx
import React from 'react'
import { useState } from 'react'
/***  函数组件对state,自身状态数据,不变时,只多渲染一次*  函数组件没有对props数据不变的情况做优化**/
function Son({count}) {let [msg, setMsg] = useState('atguigu')console.log('son render')return (<div><p>props-count: {count}</p><p>state-msg: {msg}</p><p><button onClick={()=>{setMsg('atguigu123123')}}>msg- change</button></p></div>)
}export default React.memo(Son)

13-6. useLayoutEffect[了解]

优化页面呈现,避免出现位置移动的闪屏

  • Animate.jsx
import React, { useEffect, useLayoutEffect, useRef } from 'react'
import TweenMax from 'gsap' // npm i gsap@3.7.0
import './index.css'const Animate = () => {const REl = useRef(null)useLayoutEffect(() => {/*下面这段代码的意思是当组件加载完成后,在0秒的时间内,将方块的横坐标位置移到600px的位置*/TweenMax.to(REl.current, 0, { x: 600 })}, [])return (<div className="animate"><div ref={REl} className="square">square</div></div>)
}export default Animate
  • index.css
.square{width:100px;height:100px;border:2px solid red;
}

附录

1. react代码片段

{"react模板":{"prefix": "!react","body": ["<!DOCTYPE html>","<html lang=\"en\">","<head>","\t<meta charset=\"UTF-8\">","\t<title>Title</title>","\t<script src=\"./lib/react.development.js\"></script>","\t<script src=\"./lib/react-dom.development.js\"></script>","\t<script src=\"./lib/babel.min.js\"></script>","</head>","<body>","\t<div id=\"root\"></div>","</body>","<script type=\"text/babel\">","\tconst root = ReactDOM.createRoot(document.querySelector(\"#root\"));","\troot.render((","\t\t<div></div>","\t))","</script>","</html>"],"description": "快速构建react模板页页面"}
}

1-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><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>

1-3. 虚拟DOM和真实DOM

<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>

1-4. call apply bind区别

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()

1-5. 快速创建 类组件和函数组件的快捷键

rfc: react function component

rcc: react class component

1-6. class-普通方法-箭头函数方法

普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是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>

1-3. 虚拟DOM和真实DOM

<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>

1-4. call apply bind区别

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()

1-5. 快速创建 类组件和函数组件的快捷键

rfc: react function component

rcc: react class component

1-6. class-普通方法-箭头函数方法

普通方法:是定义在原型对象上的方法,所有实例对象共用一个,但是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()

react学习笔记(完整版 7万字超详细)相关推荐

  1. 深入理解Java虚拟机(第3版)学习笔记——JAVA内存区域(超详细)

    深入理解Java虚拟机(第3版)学习笔记--JAVA内存区域(超详细) 运行时数据区域 程序计数器 java虚拟机栈 本地方法栈 java堆 方法区 运行时常量池 直接内存 对象的创建 对象的内存布局 ...

  2. go语言基础学习笔记完整版

    目录 背景 基础 helloworld 变量 常量 数据类型 基本数据类型与复杂数据类型 值类型与引用类型 查看变量类型 字符与字符串 类型转换 指针 打包 读取控制台数据 for-range遍历 生 ...

  3. Opencv学习笔记完整版

    opencv下载 https://www.raoyunsoft.com/wordpress/index.php/2020/03/09/opencvdownload/ opencv下载 https:// ...

  4. jQuery——jQuery学习笔记(完整版)

    文章目录 前言 一.关于jQuery 二.初步使用 三. jQuery选择器 四.JQUERY动画 五 .JQUERY属性操作 六.JQUERY位置操作 总结 一.关于jQuery 什么是jQuery ...

  5. 传奇开服教程完整版GOM引擎超详细的单机架设图文教程(小白一看就会)

    准备工具 1.传奇版本和补丁2.热血传奇客户端3.DBC20004.绿盟GOM登录器 服务端安装步骤: 1.把下载好的传奇版本解压在你电脑的D盘 2.补丁文件夹直接解压到你的传奇客户端根目录 3.解压 ...

  6. go语言学习笔记——godoc的使用(超详细,含示例文件)

    godoc使用教程 注释规范 注释符//后面要加空格, 例如: // xxx 在注释符要缩进的话,第二行注释符后面的空格要比上一行的空格多一个example:// 123// 123 注释要紧跟pac ...

  7. CE自带教程使用指南完整版过程(超详细)

    步骤一:下载并安装CE软件 1. 将压缩包解压后,打开Cheat Engine.exe 2. 选择>>zh_CN,点击确定. 3. 选择yes后出现了教程的步骤一界面,阅读完毕后选择下一步 ...

  8. 传奇开服教程完整版GOM引擎超详细的单机架设图文教程

    准备工具 1.传奇版本和补丁2.热血传奇客户端3.DBC20004.绿盟GOM登录器 服务端安装步骤: 1.把下载好的传奇版本解压在你电脑的D盘 2.补丁文件夹直接解压到你的传奇客户端根目录 3.解压 ...

  9. react render没更新_web前端教程分享React学习笔记(一)

    web前端教程分享React学习笔记(一),React的起源和发展:React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写 ...

最新文章

  1. 当深度学习遇上图: 图神经网络的兴起 | 赠书
  2. windows十大必禁服务
  3. Python之pandas:将dataframe数据写入到xls表格的多个sheet内(防止写入数据循环覆盖sheet表)
  4. Testing and Test-First Programming
  5. Python实现顺序表
  6. 2020,Python 已死?
  7. x学校计算机及网络维护方案,校园计算机网络常见故障的处理与维护
  8. php如果字符串有1 3 5,Day3-php 字符串1
  9. 生产环境运行Docker的9个关键决策
  10. STM32使用PWM输入模式测试频率和占空比
  11. l#039;oracle 酒,2011 Stellenbosch Vineyards Oracle of the Sun Shiraz, Stellenbosch, South Africa
  12. 微软未来五年将把80%资源投入云计算
  13. C++string类常用函数 c++中的string常用函数用法总结
  14. python: 动态网页playwright 爬虫实践
  15. Rhino6.5软件安装包详细安装教程
  16. numpy模块基础篇
  17. java转置输出_在java中如何将一个5*3的二维数组转置输出
  18. C语言算法题:一只小蜜蜂...
  19. 图像形状上下文特征ShapeContexts
  20. 42个5G智慧教育应用场景,告诉你5G将如何改变教育

热门文章

  1. python安装csv出错,用Python编写csv时出错
  2. Paypal Express Checkout快速结账API心得--Star.Hou
  3. 给定一个排好序的数组,随机输入一个数,按原顺序插入
  4. redux的使用教程
  5. 如何用idea做一个简易的网页登录界面
  6. 成功入职字节跳动!985研究生被小公司领导留了3年,辞职时领导
  7. Matlab中的向量和数组(超详细)
  8. 网络笔记(12) TCP协议(下):西行必定多妖孽,恒心智慧消磨难
  9. 新华字典 - Windows Phone
  10. 【 CSS 尺寸/宽高 属性 heightwidth 】