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

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

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. 创建虚拟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)

    }

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>atuiguname>
    <age>10age>
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>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>

2-2. jsx基本使用

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

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 是一个组件
     *      
=== > 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; //渲染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>

2-4-2. 条件渲染

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

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

    1. xxx if(){}else{}
    2. 三元表达式
  3. 多条件的: 封装成一个函数处理





    
    Title
    
    
    



    

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

  1. 表达式外部没有 “”
  2. 自定义属性也可以赋值
  3. 可以使用… 扩展运算进行批量赋值
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 操作
))

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
{/* style的值必须是一个对象:第一个{}表示的是插值表达式的边界, 第二个{}表示对象的边界 */}
我是测试行内样式的div
{/* 如果样式的单位是px,那么px可以省略,值直接写成数字即可 */}
我是测试行内样式的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((
        
c1 class 测试
bg1 class 测试
f1 class 测试
c1 bg1 同时,字符串写法
c1 bg1 同时,字符串写法

{/* 插值语法渲染数组,如果是渲染内容,那么挨个遍历渲染 如果数组是渲染className , 那数组中的每一个元素中间用 , 隔开了 */}
c1 bg1 f1 样式同时出现 数组写法
c1 bg1 f1 样式同时出现 数组写法,直接写不好使,中间有,
c1 bg1 f1 样式同时出现 数组写法
{classArr}
))

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((
    //         
{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}

  • ))}
)) }

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,说明函数的调用者是 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)
        }

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



    
    Title
    
    
    


    

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 ? 

暂无评论

: ( ) ) root.render((
{vdom}
))

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

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

  2. 单行注释

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

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

2-9. 文档碎片

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)

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学习笔记(完整版 7万字超详细)_第1张图片

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

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 (
        
App - function
) } 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.state
        return (
            

class App

count: {this.state.count}

msg: {msg}

) } }

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 (
            

count:{this.state.count}

{/*

msg:{this.state.msg}

*/}

) } }

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 (
            

App组件


) } }
  • src/components/Child.jsx
import React, { Component } from 'react'

export default class Child extends Component {
    render() {
        return (
            

Child

) } }

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 (
            

App组件


{/*通过属性传递数据给子组件*/}
) } }
  • 子组件: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.props
        return (
            

Child

props username: {this.props.username} - {username}

props age: {this.props.age}- {age}

) } }
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.state
        return (
            

App组件


子组件中接收数据,取决于属性名,而不是变量名



通过扩展运算... 批量传递状态数据

) } }
  • 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.props
        return (
            

Child

props username: {this.props.username} - {username}

props age: {this.props.age}- {age}

App state count: {num}

App state msg: {message}

) } }

4-7-2. 函数组件

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

  • Child.jsx
/**
 * 函数组件如何接收外部数据?
 * @ 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}

//
// ) // }

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.state
        return (
            

App父组件

{/* 2. 通过标签属性,传递方法 */}
) } }
  • src/components/Child.jsx [子组件是函数组件]
/**
 * 函数组件如何接收外部数据?
 * @ props: 函数组件第一个参数,就是外部数据
 * @returns 
 */
// 3. 在子组件中通过props接收从父组件传递过来的 getSonData 方法
export default function Child({ num, message, age, getSonData }) {

    return (
        

Child 函数组件

app num: {num}

app message: {message}

app age: {age}

) }
  • src/components/Child.jsx [子组件是类组件]
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}

) } }

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.state
        return (
            

App

{/* 2. 改变this指向后,将方法给子组件 */}
) } }
  • src/components/Child.jsx
import React, { Component } from 'react'

export default class Child extends Component {
    render() {
        let {setCount} = this.props
        return (
            

Child

app count props: {this.props.count}