React学习笔记汇总_part1


本人主要从事Java,仅对react进行学习与了解,前端水平一般,如有错误之处,请指定,视频学习过程的笔记,方便自己查阅,也希望对你有帮助。

声明:以下笔记内容均摘自老师的讲课视频,仅供学习使用

React18视频教程(讲师:李立超)学习笔记

以下为React视频教程学习笔记,写此笔记目的为通过学习与手敲一遍老师的笔记,加深印象,也方便查阅。
视频地址:React18视频教程(讲师:李立超)React视频教程

1.变量的声明

var、let、constant三种声明方式
三种声明方式中,第一优先使用的是const,如果希望变量被改变则使用let,至于var能不用就不用,最好不要在代码中出现。

var:没有块级作用域
let:有块级作用域
const:和let类似,具有块级作用域,但是它只能赋值一次
const使用场景:对于一些常量可以使用const声明,对于一些对象(函数)也可以使用const声明,这样可以避免对象(函数)被意外修改

示例

        {
    
    
            let a = 10;
        }
        // console.log(a);
        for(let i=0; i<5; i++){
    
    
            console.log('循环内部-->', i);
        }
        // console.log('循环外部-->', i);
        (function (){
    
    
            if(false){
    
    
                var b = 33;
            }
        })();
        if(false){
    
    
            let b = 33;
        }
        // console.log(b);
        const c = 44;
        // c = 'hello';
        const PI = 3.14;
        // console.log(c);
        const obj = {
    
    name:'孙悟空'};
        obj.age = 18;
        const fn = function (){
    
    
        };
        console.log(obj.age);

2.解构赋值

通过解构赋值,可以将数组中的元素,对象中的属性赋值给其他的变量

元素解构赋值示例

let arr = ['孙悟空', '猪八戒'];
[a, b] = arr;
const [a, b] = arr;

#函数的返回值是数组,也可以解构赋值
function fn() {
    
    
    return ['沙和尚', '唐僧'];
}
const [a, b] = fn();

arr = ['孙悟空', '猪八戒', '沙和尚', '唐僧'];
// const [a, b, ,c] = arr; // 可以跳过元素
// const [a, b, ...c ] = arr; // ...变量,会接收后边所有的元素
// console.log('a='+a, 'b='+b, 'c='+c);

const obj = {
    
    
	name: '孙悟空',
	age: 18,
	gender: '男'
};
let a, b, c;
// ({name:a, age:b, gender:c} = obj); // 将name赋值给a,age赋值给b,gender赋值给c
const {
    
    name, gender, age} = obj; // 如果变量名和属性名一致,则可以只写一个
// console.log(a, b, c);
// console.log(name, age, gender);

// 利用数组的解构来交换两个变量的位置
a = 10;
b = 20;
[a, b] = [b, a]; // 交换变量a和b的位置

arr = [1, 3, 2];
[arr[1], arr[2]] = [arr[2], arr[1]]; // 交换数组中两个元素的位置

// console.log('a =', a);
// console.log('b =', b);
console.log(arr);

3.展开

可以通过 ... 展开一个数组

        function fn(a, b, c) {
    
    
            return a + b + c;
        }

        const arr = [1, 2, 3];

        // 计算数组中三个数字的和
        let result = fn(...arr);
         // console.log(result);

        // const arr2 = [...arr]; // 相当于将arr浅复制给arr2
        const arr2 = [7, 8, 9, ...arr, 4, 5, 6];

        // console.log(arr2);

        const obj = {
    
    
            name: '孙悟空',
            age: 18,
            gender: '男'
        };

        // const obj2 = {...obj}; // 将obj在新的对象中展开,相当于浅复制
        const obj2 = {
    
    address: '花果山', ...obj, name: '猪八戒',};

        console.log(obj2);

4.箭头函数的语法

        /*
        *   箭头函数
        *       - 只有一个参数的函数
        *           参数 => 返回值
        *       - 如果没有参数,或多个参数,参数需要使用()括起来
        *           () => 返回值
        *           (a, b, c) => 返回值
        *       - 箭头后边的值就是函数的返回值
        *           - 返回值必须是一个表达式(有值的语句)
        *           - 如果返回值是对象,必须加()
        *       - 如果需要在箭头函数中定义逻辑,可以直接在箭头后跟一个代码块,
        *           代码块中语法和普通函数没有区别
        * */

示例:

        const fn = function (a){
    
    
            return 'hello';
        };

        const fn2 = a => a+'hello';
        const fn3 = (a, b) => a+'hello';

        // console.log(fn2(123));

        const sum = (a, b) => a + b;
        let result = sum(123, 456);

        const fn4 = (a, b) => {
    
    
          if(a === 10){
    
    
              a += 5;
          }else if(a === 20){
    
    
              a += 10;
          }
          return a + b;
        };

        result = fn4(10, 5);

        console.log(result);

5.箭头函数的特点

        /*
        *   1.箭头函数中没有arguments
        *   2.箭头函数中没有自己的this
        *       - 它的this总是外层作用域的this
        *   3.箭头函数中的this无法通过call()、apply()、bind()修改
        *   4.箭头函数无法作为构造函数使用
        *
        * */

示例:

       function fn(a, b, ...args){
    
    
            // arguments用来保存函数的实参
            // console.log(arguments.length);
            console.log(args);
        }
        const fn2 = (...args)=>{
    
    
            console.log(args);
        };
        const fn3 = () => {
    
    
            console.log(this);
        };
        const obj = {
    
    
            hello:()=>{
    
    
                console.log(this);
            }
        };
        const obj2 = {
    
    
            hello:function (){
    
    
                const test = () => {
    
    
                    console.log('-->',this);
                };

                test();
            }
        };
        obj2.hello();
        // new fn3();

7.类

        /*
        *   类
        *   - 类是对象的模板
        *   - 类决定了一个对象中有哪些属性和方法
        *   - 使用class关键字来定义一个类
        * */

示例:

      class Person{
    
    
            // 可以直接在类中定义属性
            // name = '孙悟空';
            // age = 18;

            // 构造函数
            // 当我们通过new创建对象时,实际上就是在调用类的构造函数
            constructor(name, age) {
    
    
                // 将参数赋值给对象中的属性
                // 在构造函数中,可以通过this来引用当前的对象
                // 在构造函数中定义属性
                this.name = name;
                this.age = age;
            }

            // 定义实例方法
            run(){
    
    
                console.log('我会跑!');
            }
        }

        const per = new Person('孙悟空', 18);
        const per2 = new Person('猪八戒', 28);
        //
        console.log(per);
        console.log(per2);
        // console.log(per === per2);
        //
        // per.run();

8.类的this

            /*
            *   类中的所有代码都会在严格模式下执行
            *       严格模式下其中一个特点就是,函数的this不在是window,而是undefined
            *
            *   注意:
            *       在类中方法的this不是固定的
            *           以方法形式调用时,this就是当前的实例
            *           以函数形式调用,this是undefined
            *       在开发时,在有些场景下,我们希望方法中的this是固定的,不会因调用方式不同而改变
            *           如果遇到上述需求,可以使用箭头函数来定义类中的方法
            *           如果类中的方法是以箭头函数定义的,则方法中的this恒为当前实例,不会改变
            *
            * */

示例

       class MyClass{
    
    
            constructor() {
    
    
                //this.fn = this.fn.bind(this); //将fn方法的this绑定为当前实例
            }
            fn(){
    
    
                console.log('-->',this);
            }
            // fn = () => {
    
    
            //     console.log('-->',this);
            // };
        }
        const mc = new MyClass();
        const test = mc.fn;
        mc.fn(); // mc
        test(); // undefined
        const fn2 = function (){
    
    
            console.log(this);
        };
        // fn2();

9.类的继承

        // 通过继承可以使得类中拥有其他类中的属性和方法
        // 使用extends来继承一个类,继承后就相当于将该类的代码复制到了当前类中
        // 当我们使用继承后,被继承的类就称为父类,继承父类的类 称为子类
        // 将多个类中的重复代码提取出来
        class Animal{
    
    
            constructor(name, age) {
    
    
                this.name = name;
                this.age = age;
            }
            sayHello = () => {
    
    
                console.log('动物在叫');
            };
        }
       class Dog extends Animal{
    
    
            /*
            *   子类继承父类后,将获得父类中所有的属性和方法,
            *       也可以创建同名的属性或方法来对父类进行重写
            * */
            sayHello = () => {
    
    
                console.log('汪汪汪!');
            };
        }
        class Snake extends Animal{
    
    
            // 当在子类中重写父类构造函数时,必须在子类构造函数中第一时间调用父类构造函数,否则会报错
            constructor(name, age, len) {
    
    
                super(name, age); // 调用父类构造函数
                this.len = len;
            }
            sayHello = () => {
    
    
                console.log('嘶嘶嘶~~');
            };
        }
        const dog = new Dog('旺财', 5);
        const snake = new Snake('长虫', 4, 10);
        // console.log(dog.name, dog.age);
        console.log(snake.name, snake.age, snake.len);
        // dog.sayHello();
        snake.sayHello();

10.静态属性和方法

        /*
        *   直接通过类调用的属性和方法被称为静态属性和静态方法
        * */
       class MyClass {
    
    
            // 使用static开头的属性是静态属性,方法是静态方法
            static name = '哈哈';
            static fn = () => {
    
    
                // 注意:静态方法this不是实例对象而是当前的类对象
                console.log(this);
            };
        }
        console.log(MyClass.name);
        MyClass.fn();

11.数组方法补充

        /*
        *   map()
        *       - 可以根据原有数组返回一个新数组
        *       - 需要一个回调函数作为参数,回调函数的返回值会成为新数组中的元素
        *       - 回调函数中有三个参数:
        *           第一个参数:当前元素
        *           第二个参数:当前元素的索引
        *           第三个参数:当前数组
        *
        *   filter()
        *       - 可以从一个数组中获得符和条件的元素
        *       - 会根据回调函数的结果来决定是否保留元素,true保留,false不保留
        *
        *   find()
        *       - 可以从一个数组中获得符和条件的第一个元素
        *
        *   reduce()
        *       - 可以用来合并数组中的元素
        *       - 需要两个参数:
        *           回调函数(指定运算规则)
        *               四个参数:
        *                   prev 上一次运算结果
        *                   curr 当前值
        *                   index 当前索引
        *                   arr 当前数组
        *           初始值
        *               - 用来指定第一次运算时prev,如果不指定则直接从第二个元素开始计算
        * */

示例

		const arr = [1, 2, 3, 4, 5];
	    let result = arr.map((item) => {
    
    
            return item + 2;
        });
        result = arr.map((item, index, array) => {
    
    
            return item + 2;
        });
        // console.log(result);
        const arr2 = ['孙悟空', '猪八戒', '沙和尚'];
        /*
        *   <li>孙悟空</li>
        *   <li>猪八戒</li>
        *   <li>沙和尚</li>
        * */
        result = arr2.map(item => "<li>" + item + "</li>");
        result = arr.filter(item => item % 2 === 0);
        result = arr.find(item => item % 2 === 0);
        result = arr.reduce((prev, curr, index) => {
    
    
            console.log(index, prev, curr);
            return prev + curr
        },0);
        console.log(result);

12.React简介

React是一个用于构建用户界面的Javascript库,起源于Facebook
,2013年开源。React还有React Native框架,通过它可以直接使用Javascript编写原生应用。

React特点:
虚拟DOM
声明式
基于组件
支持服务器端渲染
快速、简单、易学

13.HelloWorld

通过使用引入外部js的 方式来使用,需要引入react.development.js和react-dom.development.js

react.development.js
react是react核心库,只要使用react就必须要引入
下载地址:https://unpkg.com/[email protected]/umd/react.development.js
react-dom.development.js
react-dom 是react的dom包,使用react开发web应用时必须引入
下载地址:https://unpkg.com/[email protected]/umd/react-dom.development.js
注:有可能上面的两个下载地址由于网络原因下载不了,可以直接移动到最后的源码文件里面下载,效果一样。

HelloWorld.html

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
    <!--引入react的核心库-->
    <script src="script/react.development.js"></script>
    <!--引入react的DOM库-->
    <script src="script/react-dom.development.js"></script>
</head>
<body>
<div id="root"></div>
<script>  
    /*
    *   React就是用来代替DOM的
    * */
    // 通过DOM向页面中添加一个div
    // 创建一个div
    // const div = document.createElement('div'); // 创建一个dom元素
    // // 向div中设置内容
    // div.innerText = '我是一个div';
    // // 获取root
    // const root = document.getElementById('root');
    // // 将div添加到页面中
    // root.appendChild(div);
    // 通过React向页面中添加一个div
    /*
    *   React.createElement()
    *       - 用来创建一个React元素
    *       - 参数:
    *           1. 元素名(组件名)
    *           2. 元素中的属性
    *           3. 元素的子元素(内容)
    * */
    const div = React.createElement('div', {
    
    }, '我是React创建的div'); // 创建一个React元素
    // 获取根元素对应的React元素
    //  ReactDOM.createRoot() 用来创建React根元素,需要一个DOM元素作为参数
    const root = ReactDOM.createRoot(document.getElementById('root'));
    // 将div渲染到根元素中
    root.render(div);
</script>
</body>
</html>

14.三个API之一

    /*
    * React.createElement()
    *   - 用来创建一个React元素
    *   - 参数:
    *        1.元素的名称(html标签必须小写)
    *        2.标签中的属性
    *           - class属性需要使用className来设置
    *           - 在设置事件时,属性名需要修改为驼峰命名法
    *       3.元素的内容(子元素)
    *   - 注意点:
    *       React元素最终会通过虚拟DOM转换为真实的DOM元素
    *       React元素一旦创建就无法修改,只能通过新创建的元素进行替换
    * */

示例:

    // 创建一个React元素
    const button = React.createElement('button', {
    
    
        type: 'button',
        className: 'hello',
        onClick: () => {
    
    
            alert('你点我干嘛')
        }
    }, '点我一下');
    // 创建第一个div
    const div = React.createElement('div', {
    
    }, '我是一个div', button);
    // 获取根元素,根元素就是React元素要插入的位置
    const root = ReactDOM.createRoot(document.getElementById('root'));
    // 将元素在根元素中显示
    root.render(div);
    // 获取按钮对象
    const btn = document.getElementById('btn');
    btn.addEventListener('click', ()=>{
    
    
        // 点击按钮后,修改div中button的文字为click me
        const button = React.createElement('button', {
    
    
            type: 'button',
            className: 'hello',
            onClick: () => {
    
    
                alert('你点我干嘛');
            }
        }, 'click me');
        // 创建一个div
        const div = React.createElement('div', {
    
    }, '我是一个div', button);
        // 修改React元素后,必须重新对根元素进行渲染
        // 当调用render渲染页面时,React会自动比较两次渲染的元素,只在真实DOM中更新发生变化的部分
        //      没发生变化的保持不变
        root.render(div);
    });

15.三个API之二

    // 将元素在根元素中显示
    /*
    *   root.render()
    *       - 用来将React元素渲染到根元素中
    *       - 根元素中所有的内容都会被删除,被React元素所替换
    *       - 当重复调用render()时,React会将两次的渲染结果进行比较,
    *           它会确保只修改那些发生变化的元素,对DOM做最少的修改
    * */

16.JSX简介

解析jsx需要引入babel
<script src="script/babel.min.js"></script>
另外需要设置script标签属性
<script type="text/babel">

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>JSX</title>
    <script src="script/react.development.js"></script>
    <script src="script/react-dom.development.js"></script>
<!-- 引入babel -->
    <script src="script/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<!--设置js代码被babel处理-->
<script type="text/babel">
    // 创建一个React元素 <button>我是按钮</button>
    // 命令式编程
    // const button = React.createElement('button', {}, '我是按钮');
    // 声明式编程,结果导向的编程
    // 在React中可以通过JSX(JS扩展)来创建React元素,JSX需要被翻译为JS代码,才能被React执行
    // 要在React中使用JSX,必须引入babel来完成“翻译”工作
    // const button = <button>我是按钮</button>; // React.createElement('button', {}, '我是按钮');
    /*
    *   JSX就是React.createElement()的语法糖
    *       JSX在执行之前都会被babel转换为js代码
    * */
    const div = <div>
        我是一个div
        <button>我是按钮</button>
    </div>;
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(div);
</script>
</body>
</html>

17.JSX的注意

    /*
    *   JSX的注意事项
    *       1. JSX不是字符串,不要加引号
    *       2. JSX中html标签应该小写,React组件应该大写开头
    *       3. JSX中有且只有一个根标签
    *       4. JSX的标签必须正确结束(自结束标签必须写/)
    *       5. 在JSX中可以使用{}嵌入表达式
    *              - 有值的语句的就是表达式
    *       6. 如果表达式是空值、布尔值、undefined,将不会显示
    *       7. 在JSX中,属性可以直接在标签中设置
    *           注意:
    *               class需要使用className代替
    *               style中必须使用对象设置
    *                   style={
   
   {background:'red'}}
    *
    * */

18.渲染列表

    /*
    *   {} 只能用来放js表达式,而不能放语句(if for)
    *       在语句中是可以去操作JSX
    * */
    // const div = <div>Hello {name}</div>;

示例:

    let div;
    if(lang === 'en'){
    
    
        div = <div>hello {
    
    name}</div>;
    }else if(lang === 'cn'){
    
    
        div = <div>你好 {
    
    name}</div>;
    }
    const data = ['孙悟空', '猪八戒', '沙和尚'];
    /*
        <ul>
             <li>孙悟空</li>
             <li>猪八戒</li>
            ...
        </ul>
        [<li>孙悟空</li>, <li>猪八戒</li>, <li>沙和尚</li>]
    * */
    // const arr = [];
    // 遍历data
    // for(let i=0; i<data.length; i++){
    
    
    //     arr.push(<li>{data[i]}</li>);
    // }
    // const arr = data.map(item => <li>{item}</li>);
    // 将arr渲染为一个列表在网页中显示
    // jsx中会自动将数组中的元素在页面中显示
    // const list = <ul>{arr}</ul>;
    const list = <ul>{
    
    data.map(item => <li>{
    
    item}</li>)}</ul>;
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(list);

19.虚拟DOM

    /*
    *   在React我们操作的元素被称为React元素,并不是真正的原生DOM元素,
    *       React通过虚拟DOM 将React元素 和 原生DOM,进行映射,虽然操作的React元素,但是这些操作最终都会在真实DOM中体现出来
    *       虚拟DOM的好处:
    *           1.降低API复杂度
    *           2.解决兼容问题
    *           3.提升性能(减少DOM的不必要操作)
    *
    *   每当我们调用root.render()时,页面就会发生重新渲染
    *       React会通过diffing算法,将新的元素和旧的元素进行比较
    *       通过比较找到发生变化的元素,并且只对变化的元素进行修改,没有发生的变化不予处理
    * */
    //创建一个数据
    const data = ['孙悟空', '猪八戒', '沙和尚'];
    // 创建列表
    const list = <ul>
        {/*data.map(item => <li key={item}>{item}</li>)*/}
        {data.map((item, index) => <li key={index}>{item}</li>)}
    </ul>;
    // 获取根元素
    const root = ReactDOM.createRoot(document.getElementById('root'));
    // 渲染元素
    root.render(list);
    document.getElementById('btn').onclick = function (){
        // 重新渲染页面
        //创建一个数据
        const data = ['唐僧', '孙悟空', '猪八戒', '沙和尚'];
        // 创建列表
        const list = <ul>
            {/*data.map(item => <li key={item}>{item}</li>)*/}
            {data.map((item, index) => <li key={index}>{item}</li>)}
        </ul>;
        // 渲染元素
        root.render(list);
        /*
        *   旧数据
        *       ul
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *  新数据
        *       ul
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *   比较两次数据时,React会先比较父元素,父元素如果不同,直接所有元素全部替换
        *       父元素一致,在去逐个比较子元素,直到找到所有发生变化的元素为止
        *   上例中,新旧两组数据完全一致,所以没有任何DOM对象被修改
        *
        *
        * 旧数据
        *       ul
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *  新数据
        *       ul
        *           li>tom
        *           li>猪八戒
        *           li>沙和尚
        *
        *  上例中,只有第一个li发生变化,所以只有第一个li被修改,其他元素不变
        *
        *  当我们在JSX中显示数组中,数组中每一个元素都需要设置一个唯一key,否则控制台会显示红色警告
        *       重新渲染页面时,React会按照顺序依次比较对应的元素,当渲染一个列表时如果不指定key,同样也会按照顺序进行比较,
        *       如果列表的顺序永远不会发生变化,这么做当然没有问题,但是如果列表的顺序会发生变化,这可能会导致性能问题出现
        *
        *
        *   旧数据
        *       ul
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *   新数据
        *       ul
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *           li>唐僧
        *
        *   上例中,在列表的最后添加了一个新元素,并没有改变其他的元素的顺序,所以这种操作不会带来性能问题
        *
        *
        *
        *   旧数据
        *       ul
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *   新数据
        *       ul
        *           li>唐僧
        *           li>孙悟空
        *           li>猪八戒
        *           li>沙和尚
        *
        *   上例中,在列表的最前边插入了一个新元素,其他元素内容并没有发生变化,
        *       但是由于新元素插入到了开始位置,其余元素的位置全都发生变化,而React默认是根据位置比较元素
        *       所以 此时,所有元素都会被修改
        *
        *   为了解决这个问题,React为列表设计了一个key属性,
        *       key的作用相当于ID,只是无法在页面中查看,当设置key以后,再比较元素时,
        *       就会比较相同key的元素,而不是按照顺序进行比较
        *   在渲染一个列表时,通常会给列表项设置一个唯一的key来避免上述问题
        *       (这个key在当前列表中唯一即可)
        *       注意:
        *           1.开发中一般会采用数据的id作为key
        *           2.尽量不要使用元素的index作为key
        *               索引会跟着元素顺序的改变而改变,所以使用索引做key跟没有key是一样的
        *                   唯一的不同就是,控制台的警告没了
        *               当元素的顺序不会发生变化时,用索引做key也没有什么问题
        *   旧数据
        *       ul
        *           li(key=孙悟空)>孙悟空
        *           li(key=猪八戒)>猪八戒
        *           li(key=沙和尚)>沙和尚
        *   新数据
        *       ul
        *           li(key=唐僧)>唐僧
        *           li(key=孙悟空)>孙悟空
        *           li(key=猪八戒)>猪八戒
        *           li(key=沙和尚)>沙和尚
        * */
    };
    

20.手动创建React项目

后面有一个使用create-react-app自动创建项目的笔记,但老师说为了学习与了解,还是有必要走一遍手动创建的过程

  1. 新建一个目录,如react_project
  2. 进入项目所在的目录,并执行命令npm init -yyarn init -y
  3. 准备index.html和index.js
    index.html在public目录
    index.js在src目录

index.html

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>react_project</title>
</head>
<body>
    <div id="root"></div>
    <!--
        public/index.html是首页的模板,webpack在编译文件时,会以index.html为模板生成index.html
    -->
</body>
</html>

index.js

// src/index.js 是js的入口文件
// 引入ReactDOM
import ReactDOM from 'react-dom/client';

// 创建一个JSX
const App = <div>
    <h1>这是一个React项目</h1>
</div>;

// 获取一个根容器
const root = ReactDOM.createRoot(document.getElementById('root'));
// 将App渲染进根容器
root.render(App);
  1. 安装项目依赖
    npm install react react-dom react-scripts -Syarn add react react-dom react-scripts
    上面的命令会随着react的升级版本会不断变化,因此我记录下当前学习视频时用的版本的安装命令如(指定版本安装)
    npm install [email protected] [email protected] [email protected] -S
    yarn add [email protected] [email protected] [email protected]
    执行命令等待一些时间,成功后将在项目目录生成对应的node_modules目录和package.json

21.运行React项目

  1. 运行npx react-scripts start启动项目(初始启动需要输入y确认)

运行成功后会在package.json自动加入以下配置

  "browserslist": {
    
    
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
  1. package.json中增加简写启动的配置
    增加以下简写配置,这样后面启动就可以使用npm startyarn start了,同样,构建可以直接使用npm buildyarn build.
  "scripts": {
    
    
    "start": "react-scripts start",
    "build": "react-scripts build"
  },
  1. yarn start进行测试
    yarn start启动后输入http://localhost:3000进行访问
  2. 构建测试
    使用npx react-scripts buildnpm buildyarn build进行项目打包构建

22.定义练习的结构&编写样式

先参考第21节手工创建一个React项目,接着在package.json中加入eslintConfig的配置如下(此配置可以进行jsx的语法校验):

  "eslintConfig": {
    
    
    "extends": [
      "react-app"
    ]
  },

一般js和css分开写,可以通过import './index.css';引入样式表

// 引入样式表
import './index.css';
import ReactDOM from 'react-dom/client';
// 引入样式表
import './index.css';
// 创建一个React元素
const App = <div className="logs">
    {
    
    /* 日志项容器 */}
    <div className="item">
        {
    
    /* 日期的容器 */}
        <div className="date">
            <div className="month">
                四月
            </div>
            <div className="day">
                19
            </div>
        </div>
        {
    
    /* 日志内容的容器 */}
        <div className="content">
            <h2 className="desc">学习React</h2>
            <div className="time">40分钟</div>
        </div>
    </div>
</div>;
// 获取根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 渲染元素
root.render(App);

24.组件简介

/*
*   组件
*       - React中组件有两种创建方式
*       - 函数式组件
*           - 函数组件就是一个返回JSX的普通
*           - 组件的首字母必须是大写
*       - 类组件
* */

函数组件定义例子(App.js):

const App = () => {
    
    
  return <div>我是App组件!</div>
};
// 导出App
export default App;

25.类组件

/*
*   类组件必须要继承React.Component
*       相较于函数组件,类组件的编写要麻烦一下,
*           但是他俩的功能是一样的
* */
import React from "react";
class App extends React.Component{
    
    
    // 类组件中,必须添加一个render()方法,且方法的返回值要是一个jsx
    render() {
    
    
        return <div>我是一个类组件</div>;
    }
}
// 导出App
export default App;

26.事件

普通的Js的事件处理有直接在标签上写onclickonclick = function(){}、和addEventListener等方式,如下:

/**   <button onclick="alert(123)">点我一下</button>
**   <button id="btn01">点我一下</button>
**   document.getElementById('btn01').onclick = function(){};
*   document.getElementById('btn01').addEventListener('click', function(){}, false);
* */

取消默认行为

event.preventDefault(); // 取消默认行为

取消事件的冒泡

event.stopPropagation();
      /*
      *     在React中,无法通过return false取消默认行为
      *     return false;
      *      *     事件对象
      *         - React事件中同样会传递事件对象,可以在响应函数中定义参数来接收事件对象
      *         - React中的事件对象同样不是原生的事件对象,是经过React包装后的事件对象
      *         - 由于对象进行过包装,所以使用过程中我们无需再去考虑兼容性问题
      * */
    在React中事件需要通过元素的属性来设置,
      和原生JS不同,在React中事件的属性需要使用驼峰命名法:
         onclick -> onClick
         onchange -> onChange
      属性值不能直接执行代码,而是需要一个回调函数:
        onclick="alert(123)"
        onClick={()=>{alert(123)}}

28.props简介

	// 在函数组件中,属性就相当于是函数的参数,可以通过参数来访问
    // 可以在函数组件的形参中定义一个props,props指向的是一个对象
    // 它包含了父组件中传递的所有参数

在父组件中设置属性,可能是属性值,也可以是函数,子组件传参数给父组件可以通过父组传传递一个函数后,由子组件调用把数据传给父组件.

const Logs = () => {
    
    
  return <div className="logs">
    {
    
    /*在父组件中可以直接在子组件中设置属性*/}
    {
    
    /*<LogItem test="456" hello="abc" fn={()=>{}} />*/}
    <LogItem date={
    
    new Date()} desc={
    
    "学习前端"} time={
    
    "50"} />
    <LogItem date={
    
    new Date()} desc={
    
    "哈哈"} time={
    
    "30"} />
  </div>
};

在子组件中接收属性

const LogItem = (props) => {
    
    
    return (
        <div className="item">
            <MyDate/>
            {
    
    /* 日志内容的容器 */}
            <div className="content">
                {
    
    /*
                  如果将组件中的数据全部写死,将会导致组件无法动态设置,不具有使用价值
                    我们希望组件数据可以由外部设置,在组件间,父组件可以通过props(属性)向子组件传递数据
                */}
                <h2 className="desc">{
    
    props.desc}</h2>
                <div className="time">{
    
    props.time}分钟</div>
            </div>
        </div>
    );
};

29.设置日期

此节将日期变成动态数据,如在父组件中传参数

    <LogItem date={
    
    new Date(2021,7,20,19,0)} desc={
    
    "学习前端"} time={
    
    "50"} />
    <LogItem date={
    
    new Date(2022,5,22,5,30)} desc={
    
    "哈哈"} time={
    
    "30"} />

在子组件中接收参数进行解析

    //console.log(props.date.getDate());
    // 获取月份
    const month = props.date.toLocaleString('zh-CN', {
    
    month:'long'});
    // 获取日期
    const date = props.date.getDate();

接着在return中接收并使用

    return (
        <div className="date">
            <div className="month">
                {
    
    month}
            </div>
            <div className="day">
                {
    
    date}
            </div>
        </div>

30.修改Logs组件

主要是将列表的静态数据通过遍历的方式渲染出来
首先模拟好一组服务器中的数据:

// 模拟一组从服务器中加载的数据
    const logsData = [
      {
    
    
          id: '001',
          date: new Date(2021, 1, 20, 18, 30),
          desc: '学习九阳神功',
          time: 30
      },
      {
    
    
          id: '002',
          date: new Date(2022, 2, 10, 12, 30),
          desc: '学习降龙十八掌',
          time: 20
      },
  ];

接着通过遍历与jsx语法将数据渲染到页面,可以在return之外先定义好,也可以直接边遍功边输出,比如logsData.map(item => <LogItem {...item}/>),采用了展开语法将item中的属性全部传递到LogItem组件中

  // 将数据放入JSX中
  const logItemDate = logsData.map(item => <LogItem key={
    
    item.id} date={
    
    item.date} desc={
    
    item.desc} time={
    
    item.time}/>);
  return <div className="logs">
    {
    
    
    logItemDate
    //logsData.map(item => <LogItem {...item}/>)
    }
  </div>

32.State简介

  /*
  * 在React中,当组件渲染完毕后,再修改组件中的变量,不会使组件重新渲染
  *   要使得组件可以收到变量的影响,必须在变量修改后对组件进行重新渲染
  *   这里我们就需要一个特殊变量,当这个变量被修改使,组件会自动重新渲染
  *
  * state相当于一个变量,
  *   只是这个变量在React中进行了注册,
  *   React会监控这个变量的变化,当state发生变化时,会自动触发组件的重新渲染
  *   使得我们的修改可以在页面中呈现出来
  *
  * 在函数组件中,我们需要通过钩子函数,获取state
  *
  * 使用钩子 useState() 来创建state
  *   import {useState} from "react";
  *
  * 它需要一个值作为参数,这个值就是state的初始值
  *   该函数会返回一个数组
  *     数组中第一个元素,是初始值
  *       - 初始值只用来显示数据,直接修改不会触发组件的重新渲染
  *     数组中的第二个元素,是一个函数,通常会命名为setXxx
  *       - 这个函数用来修改state,调用其修改state后会触发组件的重新渲染,
  *           并且使用函数中的值作为新的state值
  * */

使用示例:

import './App.css';
import {
    
    useState} from "react";
const App = () => {
    
    
  console.log('函数执行了 ---> 组件创建完毕!');
  const [counter, setCounter] = useState(1);
  // let counter = result[0];
  // let setCounter = result[1];
  // const [counter, setCounter] = result;
  /*
  *   当点击+时,数字增大
  *   点击-时,数字减少
  * */

  // 创建一个变量存储数字
  // let counter = 2;
  const addHandler = () => {
    
    
    // 点击后数字+1
    // alert('+1');
    // counter++;
    setCounter(counter + 1); // 将counter值修改为2
  };
  const lessHandler = () => {
    
    
    // 点击后数字-1
    // alert('-1');
    // counter--;
    setCounter(counter-1);
  };
  return <div className={
    
    'app'}>
    <h1>{
    
    counter}</h1>
    <button onClick={
    
    lessHandler}>-</button>
    <button onClick={
    
    addHandler}>+</button>
  </div>;
};
// 导出App
export default App;

33.State注意事项

    /*
    *   state
    *     - state实际就是一个被React管理的变量
    *         当我们通过setState()修改变量的值时,会触发组件的自动重新渲染
    *     - 只有state值发生变化时,组件才会重新渲染
    *     - 当state的值是一个对象时,修改时是使用新的对象去替换已有对象
    *     - 当通过setState去修改一个state时,并不表示修改当前的state
    *         它修改的是组件下一次渲染时state值
    *     - setState()会触发组件的重新渲染,它是异步的
    *           所以当调用setState()需要用旧state的值时,一定要注意
    *           有可能出现计算错误的情况
    *           为了避免这种情况,可以通过为setState()传递回调函数的形式来修改state值
    * */

setState可以将最新的state值作为参数传递

            setCounter((prevCounter)=>{
    
    
                /*
                *   setState()中回调函数的返回值将会成为新的state值
                *       回调函数执行时,React会将最新的state值作为参数传递
                * */
                return prevCounter + 1;
            });

注意:修改State时,不能直接修改旧的State对象

        // 如果直接修改旧的state对象,由于对象还是那个对象,所以不会生效
        // user.name = '猪八戒';
        // console.log(user);
        // setUser(user);
        // const newUser = Object.assign({}, user);
        // newUser.name = '猪八戒';
        // setUser(newUser);

可以使用展开符将旧的State展开后再组装,比如

setUser({
    
    ...user, name: '猪八戒'});

34.DOM对象和useRef()

    /*
    *   获取原生的DOM对象
    *       1.可以使用传统的document来对DOM进行操作
    *       2.直接从React处获取DOM对象
    *           步骤:
    *               1.创建一个存储DOM对象的容器
    *                   - 使用 useRef() 钩子函数
    *                       钩子函数的注意事项:
    *                           ① React中的钩子函数只能用于函数组件或自定义钩子
    *                           ② 钩子函数只能直接在函数组件中调用
    *               2.将容器设置为想要获取DOM对象元素的ref属性
    *                   <h1 ref={xxx}>....</h1>
    *                   - React会自动将当前元素的DOM对象,设置为容器current属性
    *
    *   useRef()
    *       - 返回的就是一个普通的JS对象
    *       - {current:undefined}
    *       - 所以我们直接创建一个js对象,也可以代替useRef()
    *       - 区别:
    *           我们创建的对象,组件每次重新渲染都会创建一个新对象
    *           useRef()创建的对象,可以确保每次渲染获取到的都是同一个对象
    *
    *       - 当你需要一个对象不会因为组件的重新渲染而改变时,就可以使用useRef()
    *
    * */
    const h1Ref = useRef(); // 创建一个容器
    const [count, setCount] = useState(1);
    // const h1Ref = {current:null};
    // console.log(temp === h1Ref);
    // temp = h1Ref;
    const clickHandler = () => {
    
    
        // 通过id获取h1
        const header = document.getElementById('header');
        // alert(header);
        // header.innerHTML = '哈哈';
        console.log(h1Ref);
        // alert(h1Ref.current === header);
        h1Ref.current.innerText = '嘻嘻!';
    };
    const countAddHandler = ()=>{
    
    
      setCount(prevState => prevState + 1);
    };
    return <div className={
    
    'app'}>
        <h1 id="header" ref={
    
    h1Ref}>我是标题{
    
    count}</h1>
        <button onClick={
    
    clickHandler}>1</button>
        <button onClick={
    
    countAddHandler}>2</button>
    </div>;

35.类组件

推荐安装插件ES7+ React/Redux/React-Native snippets插件,可以在使用vscode开发react应用时,比如,rcc(React Class Component);rfc(React Functional Component);clg(console.log())等等。

    /*
    *   类组件的props是存储到类的实例对象中,
    *       可以直接通过实例对象访问
    *       this.props
    *   类组件中state统一存储到了实例对象的state属性中
    *       可以通过 this.state来访问
    *       通过this.setState()对其进行修改
    *           当我们通过this.setState()修改state时,
    *               React只会修改设置了的属性
    *
    *   函数组件中,响应函数直接以函数的形式定义在组件中,
    *       但是在类组件中,响应函数是以类的方法来定义,之前的属性都会保留
    *       但是这你仅限于直接存储于state中的属性
    *
    *   获取DOM对象
    *       1.创建一个属性,用来存储DOM对象
    *           divRef = React.createRef();
    *       2.将这个属性设置为指定元素的ref值
    * */

类使用示例代码:

import React, {
    
    Component} from 'react';
class User extends Component {
    
    
    // 创建属性存储DOM对象
    divRef = React.createRef();
    // 向state中添加属性
    state = {
    
    
        count: 0,
        test: '哈哈',
        obj: {
    
    name: '孙悟空', age: 18}
    };
    // 为了省事,在类组件中响应函数都应该以箭头函数的形式定义
    clickHandler = () => {
    
    
        // this.setState({count: 10});
        // this.setState(prevState => {
    
    
        //     return {
    
    
        //         count: prevState + 1
        //     }
        // });
        /*this.setState({
            obj:{...this.state.obj, name:'沙和尚'}
        });*/
        console.log(this.divRef);
    };
    render() {
    
    
        // console.log(this.props);
        // console.log(this.divRef);
        return (
            <div ref={
    
    this.divRef}>
                <h1>{
    
    this.state.count} --- {
    
    this.state.test}</h1>
                <h2>{
    
    this.state.obj.name} --- {
    
    this.state.obj.age}</h2>
                <button onClick={
    
    this.clickHandler}></button>
                <ul>
                    <li>姓名:{
    
    this.props.name}</li>
                    <li>年龄:{
    
    this.props.age}</li>
                    <li>性别:{
    
    this.props.gender}</li>
                </ul>
            </div>
        );
    }
}
export default User;

36.添加Card组件

这一节主要是封装一个带边框和圆角的Card容器组件,其他的组件都可以作为其子组件包裹进去使用。关键点是props.children,表示组件的标签体
Card.js:

import React from 'react';
import './Card.css';

const Card = (props) => {
    
    
    /*
    *   props.children 表示组件的标签体
    * */
    // console.log(props.children);
    return <div className={
    
    `card ${
    
    props.className}`}>{
    
    props.children}</div>;
};
export default Card;

Card.css:

.card{
    
    
    border-radius: 10px;
    box-shadow: 0 0 10px rgba(0,0,0,.2);
}

38.获取表单数据

可通过在表单域上的onChange事件定义一个函数,在处理函数里面能获取到表单域中的数据,如:

<input onChange={
    
    descChangeHandler} id="desc" type="text"/>
    let inputDesc = '';
    // 监听内容的变化
    const descChangeHandler = (e) => {
    
    
        // 获取到当前触发事件的对象
        // 事件对象中保存了当前事件触发时的所有信息
        // event.target 执行的是触发事件的对象(DOM对象)
        //console.log(e.target.value);
        inputDesc = e.target.value;
    };

如果表单是通过异步提交请求,需要取消表单的默认提交行为,通过e.preventDefault();来实现

<form onSubmit={formSubmitHandler}>
</form>
    // 当表单提交时,汇总表单中的数据
    /* 在React中,通常表单不需要自行提交
    *       而是要通过React提交
    * */
    const formSubmitHandler = (e) => {
    
    
        // 取消表单的默认行为
        e.preventDefault();
        // 获取表单项中的数据日期、内容、时长
        // 将数据拼装为一个对象
        const newLog = {
    
    
            date: new Date(inputDate),
            desc: inputDesc,
            time: +inputTime
        };
        console.log(newLog);
    };

39.双向绑定

将表单中的数据存储到state中,修改数据就调用setState方法。

        /*
        *   我们可以将表单中的数据存储到state中,
        *       然后将state设置为表单项value值,
        *       这样当表单项发生变化,state会随之变化,
        *       反之,state发生变化,表单项也会跟着改变,这种操作我们就称为双向绑定
        *       这样一来,表单就成为了一个受控组件
        * */
    const [inputTime, setInputTime] = useState('');
    //监听时长的变化
    const timeChangeHandler = (e) => {
    
    
        // 获取到当前触发事件的对象
        // 事件对象中保存了当前事件触发时的所有信息
        // event.target 执行的是触发事件的对象(DOM对象)
        //console.log(e.target.value);
        setInputTime(e.target.value);
    };

40.存储到一个state

往往我们要维护与管理的数据会很多,可以将数据都放在同一个state
比如修改前有三个数据,开始定义成:

    const [inputDate, setInputDate] = useState('');
    const [inputDesc, setInputDesc] = useState('');
    const [inputTime, setInputTime] = useState('');

可以修改成:

    // 将表单数据统一到一个state中
    const [formData, setFormData] = useState({
    
    
        inputDate:'',
        inputDesc:'',
        inputTime:''
    });

设置新数据时,需要注意将其他数据一并带过来,比如下面的...formData就是先将旧数据复制过来,然后重新设置inputDesc

        setFormData({
    
    
            ...formData,
            inputDesc: e.target.value
        });

41.完成添加的处理

在父组件中先定义好要添加的函数saveLogHandler ,传递给子组件中,然后在子组件中进行调用,操作父组件中的方法,更新父组件中的数据list。

    /*
    *     将LogsForm中的数据传递给App组件,然后App组件,将新的日志添加到数组中!
    * */

    // 定义一个函数
    const saveLogHandler = (newLog) => {
    
    
        // 向新的日志中添加id
        newLog.id = Date.now() + '';
        // console.log('App.js -->',newLog);
        // 将新的数据添加到数组中
        // logsData.push(newLog);
        setLogsData([newLog, ...logsData]);
    };
    return <div className="app">
        {
    
    /*引入LogsFrom*/}
        <LogsForm onSaveLog={
    
    saveLogHandler}/>
        <Logs logsData={
    
    logsData}/>
    </div>;

在子组件中进行调用:props.onSaveLog(newLog);

<form onSubmit={
    
    formSubmitHandler}>
</form>
    // 当表单提交时,汇总表单中的数据
    const formSubmitHandler = (e) => {
    
    
        // 取消表单的默认行为
        e.preventDefault();
        // 获取表单项中的数据日期、内容、时长
        // 将数据拼装为一个对象
        const newLog = {
    
    
            date: new Date(inputDate),
            desc: inputDesc,
            time: +inputTime
        };
        // 当要添加新的日志时,调用父组件传递过来的函数
        props.onSaveLog(newLog);
    };

42.完成删除的处理

同完成添加的处理类似

    // 定义一个函数,从数据中删除一条日志
    const delLogByIndex = (index) => {
    
    
        setLogsData(prevState => {
    
    
            const newLog = [...prevState];
            newLog.splice(index, 1);
            return newLog;
        });
    };
    return <div className="app">
        {
    
    /*引入LogsFrom*/}
        <LogsForm onSaveLog={
    
    saveLogHandler}/>
        <Logs logsData={
    
    logsData} onDelLog={
    
    delLogByIndex}/>
    </div>;

在Logs组件中使用onDelLog属性接收传进来的函数进行处理

const Logs = (props) => {
    
    
    // 将数据放入JSX中
    const logItemDate = props.logsData.map((item, index) => <LogItem
           onDelLog={
    
    ()=>props.onDelLog(index)}
           key={
    
    item.id}
           date={
    
    item.date}
           desc={
    
    item.desc}
           time={
    
    item.time}/>);

    return <Card className="logs">
        {
    
    
            logItemDate
        }
    </Card>
};

43.空列表显示

如果数据为空,logItemData.length === 0,需要格外处理下,显示没有数据

    if (logItemData.length === 0) {
    
    
        logItemData = <p className="no-logs">没要找到日志!</p>;
    }

    return <Card className="logs">
        {
    
    
            logItemData
            // logItemData.length !== 0 ? logItemData : <p className="no-logs">没要找到日志!</p>
        }
    </Card>

48.使用create-react-app

1.打开命令行
2.进行到项目所在的目录
3.使用命令`npx create-react-app 项目名`

49.内联样式和样式表

内联样式

    const pStyle = {
    
    
        color: 'red',
        backgroundColor: '#bfa',
        border: redBorder ? "red solid 1px" : "blue solid 1px"
    };

    return (
        <div>
            <p style={
    
    pStyle}>我是一个段落</p>
        </div>
    );

样式表
样式写在单独的css文件里面,在模块组件中引入

import './App.css';

50.CSS_Module

使用Css_Module可以较好的解决样式冲突问题,做法如下:

    /*
    *   CSS模块
    *       使用步骤:
    *           1.创建一个xxx.module.css
    *           2.在组件中引入css
    *               import classes from './App.module.css';
    *           3.通过classes来设置类
    *               className={classes.p1}
    *       CSS模块可以动态的设置唯一的class值
    *           App_p1__9v2n6
    * */

示例:

import React, {
    
    useState} from 'react';
import classes from './App.module.css';
const App = () => {
    
    
    return (
        <div>
            <p className={
    
    `${
    
    classes.p1} ${
    
    showBorder ? classes.Border : ''}`}>我是一个段落</p>
        </div>
    );
};
export default App;

51.Fragment

Fragment可以包裹子元素,本身又不会创建任何多余的元素。

    /*
    *   React.Fragment
    *       - 是一个专门用来作为父容器的组件
    *           它只会将它里边的子元素直接返回,不会创建任何多余的元素
    *       - 当我们希望有一个父容器
    *           但同时又不希望父容器在网页中产生多余的结构时
    *           就可以使用Fragment
    * */

如果没有用Framgment,可以定义一个Out组件

import React from 'react';

const Out = (props) => {
    
    
    return props.children;
};

export default Out;

使用时:

import React from 'react';
import Out from './Out';
const App = () => {
    
    
    return (
        <Out>
           <div>第一个组件</div>
           <div>第二个组件</div>
           <div>第三个组件</div>
        </Out>
    );
};
export default App;

有了Fragment后:

import React, {
    
     Fragment } from 'react';
const App = () => {
    
    
    return (
        <Fragment>
           <div>第一个组件</div>
           <div>第二个组件</div>
           <div>第三个组件</div>
        </Fragment>
    );
};
export default App;

或者直接用<>

import React from 'react';
const App = () => {
    
    
    return (
        <>
           <div>第一个组件</div>
           <div>第二个组件</div>
           <div>第三个组件</div>
        </>
    );
};
export default App;

53.项目准备

做一个汉堡到家的app移动应用的练习,即要支持移动应用的开发,需要准备如下
1.index.html中需要设置视口相关的meta属性

<meta name="viewport" content="width=device-width, initial-scale=1"/>

2.调整页面的整体font-size,并让宽度设置为750rem,适配不同的移动分辨率
index.js:

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from "./App";
import './index.css';

// 设置移动端的适配
// 除以几视口的宽度就是多少rem,现在我们设置视口的总宽度为750rem
document.documentElement.style.fontSize = 100 / 750 + 'vw';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>
);

App.js

const App = () => {
    
    
    return (
        <div style={
    
    {
    
    width: '750rem', height: 200, backgroundColor: '#bfa'}}></div>
    );
};
export default App;

3.其他:解决图片默认是基线对齐的缝隙,默认图片采用基线对齐,下面会有缝隙,改vertical-align属性

/**
解决图片默认是基线对齐的缝隙
*/
img{
    
    
    vertical-align: middle;
}

56.引入FontAwesome

Font Awesome,一套绝佳的图标字体库和CSS框架,这里说明下怎么在React中使用

/*
*   引入FontAwesome
*       - 安装依赖
*           npm i --save @fortawesome/fontawesome-svg-core
            npm i --save @fortawesome/free-solid-svg-icons
            npm i --save @fortawesome/free-regular-svg-icons
            npm i --save @fortawesome/react-fontawesome@latest

            yarn add @fortawesome/react-fontawesome@latest @fortawesome/free-regular-svg-icons @fortawesome/fontawesome-svg-core @fortawesome/free-solid-svg-icons

        - 引入组件
               import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
        - 引入图标
                import {faPlus} from "@fortawesome/free-solid-svg-icons";
        - 使用组件
                <FontAwesomeIcon icon={faPlus}/>
*
* */

59.Context的使用

React Context是React提供的一种跨组件传递数据的机制。它可以避免通过props一层层传递数据,使得组件之间的数据共享更加方便。

/*
*   Context相当于一个公共的存储空间,
*       我们可以将多个组件中都需要访问的数据统一存储到一个Context中,
*       这样无需通过props逐层传递,即可使组件访问到这些数据
*
*   通过React.createContext()创建context
*
* */

下面是使用React Context的基本步骤:
1.创建一个Context对象

import React from 'react';
const TestContext = React.createContext({
    
    
    name:'孙悟空',
    age:18
});
export default TestContext;

2.在父组件中提供数据,子组件会找最近的数据,比如下面的B组件,是使用{ {name:'沙和尚', age:38}}的数据的

import TestContext from "../store/testContext";

    return (
        <div>
            <TestContext.Provider value={
    
    {
    
    name:'猪八戒', age:28}}>
                <A/>
                <TestContext.Provider value={
    
    {
    
    name:'沙和尚', age:38}} >
                    <B/>
                </TestContext.Provider>
            <Meals
                mealsData={
    
    mealsData}
                onAdd={
    
    addMealHandler}
                onSub={
    
    subMealHandler}
            />
            </TestContext.Provider>
        </div>
    );

3.在子组件中使用数据

/*
*   使用方式一:
*       1.引入context
*       2.使用 Xxx.Consumer 组件来创建元素
*           Consumer 的标签体需要一个回调函数
*           它会将context设置为回调函数的参数,通过参数就可以访问到context中存储的数据
* */
import TestContext from "../store/testContext";

const A = () => {
    
    
    return (
        <TestContext.Consumer>
            {
    
    (ctx)=>{
    
    
               return <div>
                   {
    
    ctx.name} - {
    
    ctx.age}
               </div>
            }}
        </TestContext.Consumer>
    );
};
export default A;
/*
*   使用Context方式二:
*       1.导入Context
*       2.使用钩子函数useContext()获取到context
*           useContext() 需要一个Context作为参数
*               它会将Context中数据获取并作为返回值返回

*   Xxx.Provider
*       - 表示数据的生产者,可以使用它来指定Context中的数据
*       - 通过value来指定Context中存储的数据,
*           这样一来,在该组件的所有的子组件中都可以通过Context来访问它所指定数据
*
*   当我们通过Context访问数据时,他会读取离他最近的Provider中的数据,
*       如果没有Provider,则读取Context中的默认数据
* */
import TestContext from "../store/testContext";

const B = () => {
    
    
    // 使用钩子函数获取Context
    const ctx = useContext(TestContext);
    return (
        <div>
            {
    
    ctx.name} -- {
    
    ctx.age}
        </div>
    );
};
export default B;
/*
*   使用Context方式三(类组件):
*       1.导入Context
*       2.声明接收context
*          static contextType = TestContext;
*       3.使用数据
*          const {name,age} = this.context;
* */
import React, {
    
     Component } from 'react';
import TestContext from "../store/testContext";
export default class C extends Component {
    
    
    //声明接收context
	static contextType = TestContext;
    render() {
    
    
        const {
    
    name,age} = this.context;
        return (
            <div>
                <h3>我是C组件</h3>
                <h4>我接收到的: {
    
    name} -- {
    
    age} </h4>
            </div>
        );
    }
}

60.使用Context修改练习

1.创建一个Context对象

import React from 'react';
const CartContext = React.createContext({
    
    
    items: [],
    totalAmount: 0,
    totalPrice: 0,
    addItem: () => {
    
    
    },
    removeItem: () => {
    
    
    }
});
export default CartContext;

2.在父组件中提供数据

import React, {
    
    useState} from 'react';
import Meals from "./components/Meals/Meals";
import CartContext from "./store/cart-context";
const App = () => {
    
    
    return (
        <CartContext.Provider value={
    
    {
    
    ...cartData, addItem, removeItem}}>
            <div>
                <Meals
                    mealsData={
    
    mealsData}
                />
            </div>
        </CartContext.Provider>
    );
};
export default App;

3.在子组件中使用数据

import React, {
    
    useContext} from 'react';
import CartContext from "../../../store/cart-context";

const Counter = (props) => {
    
    

    // 获取cartContext
    const ctx = useContext(CartContext);

    // 添加购物车的函数
    const addButtonHandler = () => {
    
    
        ctx.addItem(props.meal);
    };
    return (
        <div>
            <button
                onClick={
    
    addButtonHandler}
                className={
    
    classes.Add}>
                <FontAwesomeIcon icon={
    
    faPlus}/>
            </button>
        </div>
    );
};
export default Counter;

62.完成搜索

定义一个过滤的函数filterHandler,此函数通过数组的方法filter进行搜索过滤

    // 创建一个过滤meals的函数
    const filterHandler = (keyword) => {
    
    
        const newMealsData = MEALS_DATA.filter(item => item.title.indexOf(keyword) !== -1);
        setMealsData(newMealsData);
    };

然后将filterHandler传递给子组件进行调用

<FilterMeals onFilter={
    
    filterHandler}/>

在子组件中使用

const FilterMeals = (props) => {
    
    
    const inputChangeHandler = e => {
    
    
        const keyword = e.target.value.trim();
        props.onFilter(keyword);
    };
    return (
        <div className={
    
    classes.FilterMeals}>
            <div className={
    
    classes.InputOuter}>
                <input
                    onChange={
    
    inputChangeHandler}
                    className={
    
    classes.SearchInput}
                    type="text"
                    placeholder={
    
    "请输入关键字"}/>
            </div>
        </div>
    );
};

63.处理字体大小问题

为了处理不同的屏幕显示的文字和图片,应该设置字体为相对单位rem
rem是相对于顶层html元素的大小来的,框架中是这样处理的

// 设置移动端的适配
// 除以几视口的宽度就是多少rem,现在我们设置视口的总宽度为750rem
document.documentElement.style.fontSize = 100 / 750 + 'vw';

设置大小的地方采用rem来表示,如:

.Price{
    
    
    font-weight: bold;
    font-size: 36rem;
}

66完成Backdrop

通过ReactDOM.createPortal进行遮罩层的处理,ReactDOM.createPortal是React中的一个方法,它允许你将一个React组件渲染到DOM树的不同部分,超出父组件的层次结构之外。当你想要将组件渲染到DOM树中的特定位置时,它非常有用。
1.定义好backdrop-root

<div id="backdrop-root"></div>

2.渲染到backdrop-root

import React from 'react';
import ReactDOM from 'react-dom';
import classes from './Backdrop.module.css';
const backdropRoot = document.getElementById('backdrop-root');
const Backdrop = (props) => {
    
    
    return ReactDOM.createPortal(
        <div className={
    
    `${
    
    classes.Backdrop} ${
    
    props.className}`}>
            {
    
    props.children}
        </div>,backdropRoot
    );
};
export default Backdrop;

67.setState()的执行流程(函数组件)

    /*
    *   - setState()的执行流程(函数组件)
    *       setCount() --> dispatchSetDate()
    *                       --> 会先判断,组件当前处于什么阶段
    *                   如果是渲染阶段 --> 不会检查state值是否相同
    *                   如果不是渲染阶段 --> 会检查state的值是否相同
    *                       - 如果值不相同,则对组件进行重新渲染
    *                       - 如果值相同,则不对组件进行重新渲染
    *                           如果值相同,React在一些情况下会继续执行当前组件的渲染
    *                               但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际的效果
    *                               这种情况通常发生在值第一次相同时
    *
    * */

68.useEffect

// useEffect()是一个钩子函数,需要一个函数作为参数
//      这个作为参数的函数,将会在组件渲染完毕后执行
//  在开发中,可以将那些会产生副作用的代码编写到useEffect的回调函数中
//      这样就可以避免这些代码影响到组件的渲染
import React, {
    
    useEffect, useState} from 'react';
const App = () => {
    
    
    const [count, setCount] = useState(0);
    useEffect(()=>{
    
    
        setCount(1);
    });
    return (
        <div>
            {
    
    count}
        </div>
    );
};
export default App;
   /*
    *   默认情况下,useEffect()中的函数,会在组件渲染完成后调用,
    *       并且是每次渲染完成后都会调用
    *
    *   在useEffect()可以传递一个第二个参数,
    *       第二个参数是一个数组,在数组中可以指定Effect的依赖项
    *       指定后,只有当依赖发生变化时,Effect才会被触发
    *       通常会将Effect中使用的所有的局部变量都设置为依赖项
    *           这样一来可以确保这些值发生变化时,会触发Effect的执行
    *       像setState()是由钩子函数useState()生成的
    *           useState()会确保组件的每次渲染都会获取到相同setState()对象
    *           所以setState()方法可以不设置到依赖中
    *       如果依赖项设置了一个空数组,则意味Effect只会在组件初始化时触发一次
    * */
    useEffect(()=>{
    
    
        if(ctx.totalAmount === 0){
    
    
            // 购物车已经被清空
            setShowDetails(false);
            setShowCheckout(false);
        }
    },[ctx]);

69.useEffect的清理函数

通过useEffect的清理函数来降低执行的次数。

    // 通过Effect来改造练习
    useEffect(()=>{
    
    

        // 降低数据过滤的次数,提高用户体验
        // 用户输入完了你在过滤,用户输入的过程中,不要过滤
        // 当用户停止输入动作1秒后,我们才做查询
        // 在开启一个定时器的同时,应该关掉上一次
        const timer = setTimeout(()=>{
    
    
            console.log('Effect触发了!');
            props.onFilter(keyword);
        }, 1000);

        // 在Effect的回调函数中,可以指定一个函数作为返回值
        // 这个函数可以称其为清理函数,它会在下次Effect执行前调用
        // 可以在这个函数中,做一些工作来清除上次Effect执行所带来的的影响
        return () => {
    
    
            clearTimeout(timer);
        };

    }, [keyword]);

未完待续,可详见源码下载里面

源码下载

1.React18视频教程(讲师:李立超)学习笔记源码

github: https://github.com/jxlhljh/liuuistudytest/tree/master/liureacttest/react_hook_test
gitee: https://gitee.com/jxlhljh/liuuistudytest/tree/master/liureacttest/react_hook_test

猜你喜欢

转载自blog.csdn.net/jxlhljh/article/details/131172592