【前端学习笔记】React基础

160 阅读15分钟

1. React 简介

React是什么

React是一个将数据渲染为HTML视图的开源JavaScript库 只关注操作DOM呈现页面的工作

为什么要学React(原生js的缺点)

  • 原生JavaScript操作DOM繁琐,效率低(DOM-API操作UI)
  • 原生JavaScript直接操作DOM,浏览器会进行大量的重绘重排
  • 原生JavaScript没有组件化的编码方案,代码复用率低

React的特点

  • 采用组件化模式,声明式代码,提高开发效率及组件复用率
  • 在React Native中可以使用React语法进行移动端开发
  • 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互

2. React入门

Hello,React

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

<body>
    <!-- 准备好一个容器 -->
    <div id="test"></div>
    <div id="demo"></div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        // 1. 创建虚拟DOM
        // 这个虚拟DOM就是一个一般对象object
        const VDOM= <h1 id="title">Hello,React</h1>  /*此处一定不要写引号,因为不是字符串*/

        const TDOM=document.getElementById('demo')
        // 2. 渲染虚拟DOM到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(VDOM,document.getElementById('test'))

        // console.log('虚拟DOM',VDOM);
        // console.log('真实DOM',TDOM);

        console.log(typeof VDOM);   //object
        console.log(typeof TDOM);   //object
        //1. 关于虚拟DOM本质是一个一般对象
        // 2. 虚拟DOM比较轻,真实DOM比较重,因为虚拟DOM是react内部在用,无需真实DOM上那么多的属性
        // 3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上
    </script>
</body>

</html>

jsx

jsx全称JavaScript XML,是React定义的一种类似于XML的js扩展语法:js+XML

jsx核心规则

  1. 定义虚拟DOM时,不要写引号
  2. 标签中混入js表达式时要用{}
  3. 样式的类名指定不要用class,要用className
  4. 内联样式要用style={{key1:'value1',key2:'value2'}}的形式去写
  5. 只有一个根标签
  6. 标签必须闭合
  7. 标签首字母: (1) 如果小写字母开头,则将该标签转为HTML中同名元素,若HTML中无同名元素,则报错 (2) 如果大写字母开头,react就去渲染对应的组件,如果组件没有定义,则报错

jsx代码演示

<!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>
    <style>
        .title {
            background-color: yellow;
            width: 200px;
        }
    </style>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        const myId='SpongebobSquarePants'
        const myData='Hello,React'
        // 1. 创建虚拟DOM
        const VDOM= (
          <div>
            <h2 id={myId} className="title">
                <span style={{color:'pink',fontSize:'30px'}}>{myData}</span>
            </h2>
            <input type="text"></input>
        </div>
        )
        // 2. 渲染虚拟DOM到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(VDOM,document.getElementById('test'))


        // jsx语法规则:
        // 1.定义虚拟DOM时,不要写引号
        // 2. 标签中混入js表达式时要用{}
        // 3. 样式的类名指定不要用class,要用className
        // 4. 内联样式要用style={{key1:'value1',key2:'value2'}}的形式去写
        // 5. 只有一个根标签
        // 6. 标签必须闭合
        // 7. 标签首字母:
        //     (1) 如果小写字母开头,则将该标签转为HTML中同名元素,若HTML中无同名元素,则报错
        //     (2)如果大写字母开头,react就去渲染对应的组件,如果组件没有定义,则报错
         


    </script>
</body>

</html>

一个注意点: 虚拟DOM内不可以写语句(代码),只可以写表达式

补充知识点:语句与表达式

  1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方。例如: (1) a (2) a+b (3) demo(1) (4) arr.map() (5) function test (){}

  2. 语句(代码),例如: (1) if(){} (2) for(){} (3) switch(){case:xxx}

可以简单认为,可以返回一个值的都是表达式;而语句(代码)控制代码的走向

模块与组件,模块化与组件化

模块

向外提供特定功能的js程序就是一个模块,一般就是一个js文件
为什么要拆成模块?

因为随着业务逻辑增加,代码越来越多且复杂 将js拆成一个个模块,可以复用js,简化js编写,提高js运行效率

组件

用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)

使用组件可以复用代码,简化项目代码,提高运行效率

模块化

当应用的js都以模块来编写,这个应用就是模块化的应用

组件化

当应用是以多组件的方式实现,这个应用就是一个组件化的应用

React面向组件编程

组件

两种定义组件的方式:

  • 函数式组件
  • 类式组件

函数式组件

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

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        // 1. 创建函数式组件
        function Demo(){
            console.log(this);  //此处的this是undefined,是因为babel编译后开启了严格模式
            return <h2>我是用函数定义的组件(适用于简单组件的定义)</h2>
        }
       
        // 2. 渲染组件到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(<Demo/>,document.getElementById('test'))

        /*执行了 ReactDOM.render(<Demo/>,document.getElementById('test')) 之后发生了什么?
        1. react解析组件标签,找到了Demo组件
        2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转换为真实DOM,呈现在页面中
        */
    </script>
</body>

</html>

类式组件

类相关知识复习

总结:

  • 类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加属性时,才写
  • 如果A类继承了B类,且A类中写了构造器,那么A类构造器中super是必须要调用的
  • 类中定义的方法,都是放在了类的原型对象上,供实例使用
<!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>
</head>

<body>
    <script type="text/javascript">
        // 创建一个Person
        class Person {
            // 构造器方法
            constructor(name, age) {
                // 构造器中的this指的是谁?————类的实例对象
                this.name = name
                this.age = age
            }

            // 一般方法
            // speak方法放在了哪里?————类的原型对象上,供实例使用
            // 通过Person实例调用speak时,speak中的this就是Person实例
            speak() {
                console.log(`我叫${this.name},今年${this.age}岁`)
            }
        }

        // 创建一个person的实例对象
        // const p1 = new Person('tom', 18)

        // console.log(p1);
        // p1.speak()


        // 创建一个student类,继承于Person类
        class Student extends Person {
            constructor(name, age, grade) {
                super(name, age)
                this.grade = grade

            }

            // 重写从父类继承过来的方法
            speak() {
                console.log(`My name is ${this.name},I am ${this.age} years old,and I am a grade${this.grade}student`);
            }

            study(){
                console.log('I study hard');
            }
        }

        const s1 = new Student('student', 3, '高一')
        console.log(s1);
        s1.speak()
        s1.study()
    </script>
</body>

</html>
类式组件理解
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        //  创建类式组件
        class MyComponent extends React.Component{
            // render是放在哪里的?——MyComponent的原型对象上,供实例使用
            render(){
                // render中的this是谁?——MyComponent的实例对象<=>Mycomponent组件实例对象
                console.log(this);
            return <h2>我是用类定义的组件(适用于复杂组件的定义)</h2>
            }
        }

         
        // 2. 渲染组件到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(<MyComponent></MyComponent>,document.getElementById('test'))

        // 执行了 ReactDOM.render(<MyComponent></MyComponent>,document.getElementById('test'))之后发生了什么?

        // 1. React解析组件标签,找到了MyComponent组件
        // 2. 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上render方法
        // 3. 将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
    </script>
</body>

</html>

state

  • 函数式组件:是用函数定义的组件,适用于简单组件的定义
  • 类式组件,是用类定义的组件,适用于复杂组件的定义

这里的简单组件和复杂组件的区分标准是看其有无state state是组件实例的三大核心属性之一

也就是说只有类式组件有state这个属性,函数式组件没有实例的概念,也就没有state这个属性

复习:原生js绑定事件

原生JavaScript绑定事件的三种方式:

<!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>
</head>
<body>
        <button id="btn1">button1</button>
        <button id="btn2">button2</button>
        <button id="btn3" onclick="demo()">button3</button>
        

        <script>
            const btn1=document.getElementById('btn1')
            btn1.addEventListener('click',()=>{
                alert('按钮1被点击了')
            })

            const btn2=document.getElementById('btn2')
            btn2.onclick=()=>{
                alert('按钮2被点击了')
            }

            function demo(){
                alert('按钮3被点击了')
            }
        </script>
</body>
</html>
React绑定事件
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        //  创建类式组件
        class Weather extends React.Component{
            constructor(props){
                super(props)
                this.state={isHot:true}

            }
            // render是放在哪里的?——MyComponent的原型对象上,供实例使用
            render(){
                // render中的this是谁?——MyComponent的实例对象<=>Mycomponent组件实例对象
             const {isHot} =this.state
            //  React里面如何绑定事件? onclick=>onClick ;   demo()=>demo
            return <h2 onClick={demo}>今天天气很{isHot?'炎热':'凉爽'}</h2>
            }

            
        }

         
        // 2. 渲染组件到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(<Weather></Weather>,document.getElementById('test'))

        // const title=document.getElementById('title')   
        // title.onclick=()=>{
        //     console.log('我被点击了');
        // }

        function demo(){
            console.log('我被点击了');
        }
    </script>
</body>

</html>
初识state
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        //  创建类式组件
        class Weather extends React.Component{
            // 构造器调用几次?——一次
            constructor(props){
                super(props)
                // 初始化状态
                this.state={isHot:true,wind:'微风'}

                // 解决changeWeather中this指向问题
                this.changeWeather=this.changeWeather.bind(this)

            }
            // render是放在哪里的?——MyComponent的原型对象上,供实例使用


            // render调用几次?——1+n次 1次是初始化的那次,n是状态更新的次数
            render(){
                console.log('render');
                // render中的this是谁?——MyComponent的实例对象<=>Mycomponent组件实例对象
             const {isHot,wind} =this.state
            //  React里面如何绑定事件? onclick=>onClick ;   demo()=>demo
            return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</h2>
            }


            // changeWeather调用几次?——点几次调几次
            changeWeather(){
            // console.log('我被点击了');
            // changeWeather放在哪里了?——Weather的原型对象上,供实例使用
            // 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
            // 类中的方法默认开启了局部的严格模式,所以changeWeather中的this是undefined

            // console.log(this.state.isHot);
            // console.log(this);
            // 获取原来的isHot值
            const isHot=this.state.isHot
            // 严重注意:状态必须通过setState进行修改,且更新是一种合并,不是替换
            this.setState({isHot:!isHot})
            
            // 严重注意:状态(state)不可直接更改,要借助一个内置的API去更改
            // 下面就是直接更改:
            // this.state.isHot=!isHot
            }

            
        }

        // 2. 渲染组件到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(<Weather></Weather>,document.getElementById('test'))

        // const title=document.getElementById('title')   
        // title.onclick=()=>{
        //     console.log('我被点击了');
        // }
    </script>
</body>

</html>
精简state(对上面的代码进行简写)
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        class Weather extends React.Component{
       
            // 初始化状态
            state={isHot:true,wind:'微风'}
            render(){
              const {isHot,wind}=this.state
            return <h2 onClick={this.changeWeather}>今天天气很{isHot?'炎热':'凉爽'},{wind}</h2>
            }
            // 自定义方法——要用赋值语句的形式+箭头函数
            changeWeather=()=>{
            const isHot=this.state.isHot
            this.setState({isHot:!isHot})
            
            }

            
        }

        ReactDOM.render(<Weather></Weather>,document.getElementById('test'))

    </script>
</body>

</html>
总结
  • state是组件对象的最重要的属性,值是对象(可以包含多个Key-value的组合)
  • 组件被称为状态机,通过更新组件的state来更新对应的页面显示(重新渲染组件)

注意:

  • 组件中render方法中的this为组件实例对象
  • 组件自定义方法中this为undefined,如何解决?
    • 强制绑定this:通过函数对象的bind()
    • 箭头函数
  • 状态数据,不能直接修改或者更新

props

初识props
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        // 创建组件
    class Person extends React.Component {
        render(){
            console.log(this);
            const {name,age,sex}=this.props
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }
    }

    // 渲染组件
    ReactDOM.render(<Person name="tom" age="18" sex="female"></Person>,document.getElementById('test'))
    </script>
</body>

</html>
复习:展开运算符
<!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>
</head>

<body>
    <script>
        let arr1 = [1, 2, 3, 4, 5]
        let arr2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
        // 展开一个数组
        // console.log(...arr1);

        // 连接数组
        let arr3 = [...arr1, ...arr2]
        // console.log(arr3);

        // 函数传参
        function sum(...numbers) {
            // return a+b
            // console.log(numbers);
            return numbers.reduce((preValue, currentValue) => {
                return preValue + currentValue
            })
        }
        // console.log(sum(1, 1, 3, 3, 12, 412, 5, 3, 56));


        // 构造字面量对象时使用展开语法
        let person = {
            name: 'John',
            age: 123,
            firend: {
                firend1: {
                    name: "f1",
                    age: 3
                },
                friend2: {
                    name: "f2",
                    age: 4
                }

            }
        }
        // console.log(...person);    //报错,展开运算符不能展开对象
        let person2 = {
            ...person
        } //可以展开
        console.log(person2);


        // 合并
        let person3 = {
            ...person,
            name: "Name",
            tel: 13888888888
        }
        // console.log(person3);
    </script>
</body>

</html>
批量传递props
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        // 创建组件
    class Person extends React.Component {
        render(){
            console.log(this);
            const {name,age,sex}=this.props
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }
    }
  
    // 渲染组件
     const p={name:'Lily',age:87,sex:'male'}
    ReactDOM.render(<Person {...p}></Person>,document.getElementById('test'))
    </script>
</body>

</html>

也叫批量传递标签属性

props的简写方式
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>
    <!-- 引入 prop-types,用于对组件标签属性进行限制 -->
    <script src="../js/prop-types.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        // 创建组件
    class Person extends React.Component {
        
    // 对标签属性进行类型和必要性的限制
        static propTypes={
            name:PropTypes.string.isRequired,
            sex:PropTypes.string,
            age:PropTypes.number,
            speak:PropTypes.func,

        }

    // 指定默认的标签属性值
        static defaultProps={
            sex:'不男不女',
            age:1
        }

        render(){
            console.log(this);
            const {name,age,sex}=this.props  

            // props是只读的
            // this.props.name="readonlytest" //报错,因为props是只读的
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }
    }

    // 渲染组件
     const p={name:'haha',age:87,sex:'male'}
    ReactDOM.render(<Person {...p} speak={speak}></Person>,document.getElementById('test'))

    function speak(){
        console.log('speaking...');
    }
    </script>
</body>

</html>

关于在类式组件中是否要写constructor,是否在constructor中传入props的问题: 构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props 但是在构造器中通过this访问props的情况极其罕见

总之:如果没有极特殊的情况,类中的构造器能不写就不写

函数式组价使用props
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>

    <script src="../js/prop-types.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        function Person(props){
            const {name,age,sex}=props
            return (
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
            )
        }

        Person.prototype={
            name:PropTypes.string.isRequired,
            sex:PropTypes.string,
            age:PropTypes.number,
        }

        Person.defaultProps={
            sex:'male',
            age:18
        }
  
        // 2. 渲染虚拟DOM到页面
        // ReactDOM.render(虚拟DOM,容器)
        ReactDOM.render(<Person name='name' ></Person>,document.getElementById('test'))        
    </script>
</body>

</html>
props理解
  • 每个组件对象都会有props属性
  • 组件标签的所有属性都保存在props中
  • 通过标签属性从组件外向组件内传递变化的数据
  • 组件内部不要修改props数据

refs

字符串形式的回调函数
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->   
     <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        class Demo extends React.Component{
            showData=()=>{
                // console.log(this.refs.input1);
                const {input1}=this.refs
                alert(input1.value)
            }

            showData2=()=>{
                const {input2}=this.refs
                alert(input2.value)
            }
            render(){
                return(
                    <div>
                        <input ref="input1" type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                        <input ref="input2" onBlur={this.showData2} type="text"  placeholder="失去焦点提示数据"/>
                    </div>
                )
            }

        }
        ReactDOM.render(<Demo />,document.getElementById('test'))
    </script>
</body>

<html>
回调函数形式的ref
<!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>
</head>

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->   
     <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        class Demo extends React.Component{
            showData=()=>{
                // console.log(this.refs.input1);
                const {input1}=this
                alert(input1.value)
            }

            showData2=()=>{
                const {input2}=this
                alert(input2.value)
            }
            render(){
                return(
                    <div>
                        <input ref={(currentNode)=>{this.input1=currentNode}} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                        <input ref={c=>this.input2=c} onBlur={this.showData2} type="text"  placeholder="失去焦点提示数据"/>
                    </div>
                )
            }

        }
        ReactDOM.render(<Demo />,document.getElementById('test'))
    </script>
</body>

</html>                                                                   
createRef

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

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->   
     <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">
        class Demo extends React.Component{
            // React.createRef()  调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
            
            myRef=React.createRef()
            showData=()=>{
                // console.log(this.refs.input1);
                // const {input1}=this
                // alert(input1.value)
                // console.log(this.myRef.current.value);
                alert(this.myRef.current.value)
            }

            showData2=()=>{
                const {input2}=this
                alert(input2.value)
            }
            render(){
                return(
                    <div>
                        <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                        <input ref={c=>this.input2=c} onBlur={this.showData2} type="text"  placeholder="失去焦点提示数据"/>
                    </div>
                )
            }

        }
        ReactDOM.render(<Demo />,document.getElementById('test'))
    </script>
</body>

</html>                                                                                 
ref总结
  • 在条件允许的情况下,尽量避免字符串形式的ref
  • 内联形式的函数是在实际开发中比较常用的
  • createRef是目前React最推荐的一种创建ref的形式
React中的事件处理
  • 通过onXxx属性指定事件处理函数,注意大小写
    • React中使用的是自定义(合成)事件,而不是使用的原生DOM事件,这样做是为了更好的兼容性
    • React中的事件是通过事件委托方式处理的(委托给组件最外层的元素),这样做是为了高效
  • 可以通过event.target得到发生事件的DOM元素对象,这样可以避免过渡的使用ref

React中收集表单数据

非受控组件

所有输入类DOM的值现用现取的组件是非受控组件

受控组件

所有输入类DOM的值随着输入就把输入的内容维护到状态(state)中,等需要用时从状态(state)中取就是受控组件

高阶函数与函数柯里化

高阶函数:如果一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数:
1. 如果A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
2. 若A函数,调用的返回值是一个函数,那么A就可以称之为高阶函数

常见的高阶函数:

  • Promise

  • setTimeout

  • arr.map()

  • ...

    函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式

函数的柯里化示例:

<!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>
</head>
<body>
    <script>
        function sum(a){
            return (b)=>{
                return (c)=>{
                    return a+b+c
                }
            }
        }
        const res=sum(1)(2)(3)
        console.log(res);
    </script>
</body>
</html>

组件的生命周期

生命周期回调函数<=>生命周期钩子函数<=>生命周期函数<=>生命周期钩子

理解:

  • 组件从创建到死亡它会经历一些特定的阶段
  • React组件中包含一系列钩子函数,会在特定的时刻调用
  • 我们在定义组件时,会在特定的生命周期回调函数中做特定的工作
生命周期钩子

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

<body>
    <!-- 准备好一个容器 -->
    <div id="test">
    </div>

    <!-- 引入react核心库 -->
    <script src="../js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script src="../js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js引入 -->
    <script src="../js/babel.min.js"></script>


    <!-- 此处一定要写babel -->
    <script type="text/babel">

        class Count extends React.Component{
            constructor(props){
                console.log('Count-constructor');
                super(props)
                // 初始化状态
                this.state={count:0}
            }
    

            add=()=>{
                // 获取原来状态
                const {count}=this.state
                // 更新状态
                this.setState({count:count+1})
            }

            // 卸载组件按钮的回调
            death=()=>{
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

            // 强制更新按钮的回调
            force=()=>{
                this.forceUpdate()
            }

            // 组件将要挂载的钩子
            componentWillMount(){
                console.log('Count-componentWillMount');
            }

            // 组件挂载完毕的钩子
            componentDidMount(){
                console.log('Count-componentDidMount');
            }
            
            // 组件将要卸载的钩子
            componentWillUnmount(){
                console.log('Count-componentWillUnmount');
            }

            // 控制组件更新的阀门
            shouldComponentUpdate(){
                console.log('Count-shouldComponentUpdate');
                return false   
            }

            // 组件将要更新的钩子
            componentWillUpdate(){
                console.log('Count-componentWillUpdate');
            }

            // 组件更新完毕的钩子
            componentDidUpdate(){
                console.log('Count-componentDidUpdate');
            }
        

            render(){
                console.log('Count-render');
                const {count}=this.state
                return(
                    <div>
                        <h2>当前求和为:{count}</h2>
                        <button onClick={this.add}>点我加1</button>
                        <button onClick={this.death}>卸载组件</button>
                        <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
                    </div>
                )

            }
        }
        

        // 父组件A 
        class A extends React.Component{

            state={carName:'benz'}

            changeCar=()=>{
                this.setState({carName:'BMW'})

            }

            render(){
                return (
                   <div>
                    <div>A</div>
                    <button onClick={this.changeCar}>换车</button>
                    <B carName={this.state.carName}></B>
                   </div>
                )
            }
        }

        // 子组件B 
        class B extends React.Component{
            // 组件将要接收新的props的钩子
            componentWillReceiveProps(props){
                console.log('B-componentWillReceiveProps',props);
            }
            
            // 控制组件更新的阀门
            shouldComponentUpdate(){
                return true
                console.log('B-shouldComponentUpdate');
            }

            // 组件将要更新的钩子
            componentWillUpdate(){
                console.log('B-componentWillUpdate');
            }

            // 组件更新完毕的钩子
            componentDidUpdate(){
                console.log('B-componentDidUpdate');
            }

            render(){
                console.log('B-render');
                return(
                    <div>我是B组件,接收到的车是{this.props.carName}</div>
                )
            }
        }
        // ReactDOM.render(<Count ></Count>,document.getElementById('test'))
        ReactDOM.render(<A ></A>,document.getElementById('test'))
    </script>
</body>

</html>

生命周期(旧)分为三个阶段:

  1. 初始化阶段:有ReactDOM.render()触发——初次渲染
    1. constructor()
    2. componentWillMount()
    3. rnder()
    4. componentDidMount()——常用,一般在这里做一些初始化的事,例如:开启定时器,发送网络请求、订阅消息等
  2. 更新阶段:由组件内部this.setState()或者父组件重新render触发
    1. shouldConponentUpdate()
    2. componentWillUpdate()
    3. render()
    4. componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentAtNode触发
    1. componentWillUnmount()——常用:一般做一些收尾的事,例如:关闭定时器、取消订阅

生命周期(新)的三个阶段:

  1. 初始化阶段:由ReactDOM.render()触发——初次渲染
    1. constructor()
    2. getDerivedStateFromProps
    3. render()
    4. componentDidMount()
  2. 更新阶段:由组件内部this.setState()或父组件重新render触发
    1. getDerivedStateFromProps
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate()
    5. componentDidUpdate()
  3. 卸载组件:由ReactDOM.unmountComponentAtNode()触发
    1. componentWillUnmount()

三个重要的钩子:

  • render:初始化渲染或者更新渲染调用
  • componentDidMount:开启监听,发送Ajax请求
  • componentWillUnmount:做一些收尾工作,如:清理定时器

即将废弃的钩子:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

DOM 中Diff算法

key的作用

经典面试题:

  • react/vue中的key有什么作用(key的内部原理是什么)
  • 为什么遍历列表时,key最好不要用index?

虚拟DOM中key的作用: 简单的说:key是虚拟DOM 对象的标识,在更新显示时key起着极其重要的作用 详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM 随后React进行新虚拟DOM与旧虚拟DOM的Diff比较,比较规则如下:

  • 旧虚拟DOM中找到了与新虚拟DOM相同的key:
    • 若虚拟DOM中内容没变,直接使用之前的真实DOM
    • 若虚拟DOM中内容变了,则生成新的真实的DOM,随后替换掉页面中之前的真实DOM
  • 旧虚拟DOM中未找到与新虚拟DOM相同的key
    • 根据数据创建新的真实DOM,随后渲染到页面

用index作为key可能会引发的问题:

  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作: 会产生没有必要的真实DOM更新,从而导致效率变低
  2. 如果结构中还包含输入类的DOM: 会产生错误DOM更新,界面会产生问题
  3. 如果不存在对数据的逆序添加、逆序删除等操作,仅用于渲染列表用户展示,使用index作为key 是没有问题的

开发中如何选择key?

  1. 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值
  2. 如果确定只是简单的展示数据,用index作为key也是可以的

React应用(基于脚手架)

  • React提供了一个用于创建react项目的脚手架库:create-react-app
  • 项目的整体技术架构为:react+webpack+es6+eslint
  • 使用脚手架开发的项目的特点:模块化,组件化,工程化

创建项目并启动

  1. 全局安装npm i -g create-react-app
  2. 切换到想创建项目的目录,使用命令:create-react-app project_name
  3. 进入项目文件夹:cd project_name
  4. 启动项目:npm start

备注:在2022年4月15日尝试使用create-react-app project_name发现react已经不支持使用这种方式创建一个react新项目:

We no longer support global installation of Create React App.

Please remove any global installs with one of the following commands:
- npm uninstall -g create-react-app
- yarn global remove create-react-app

所以将create-react-app卸载,使用npm init react-app my-app创建新项目

脚手架文件介绍

index.html

%PUBLIC_URL%代表public文件夹的路径

用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器):

<meta name="theme-color" content="red" />

用于指定网页添加到手机主屏幕后的图标(仅支持苹果手机):

<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png">

应用加壳时的配置文件:

<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />

浏览器不支持js则展示标签中的内容:

<noscript>...</noscript>
robots.txt

爬虫相关协议规则文件

TodoList案例

总结:

  1. 动态初始化列表,如何确定将数据放在那个组件的state中?
    • 某个组件使用:放在自身的state中
    • 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  2. 父子之间通信:
    • 父组件给子组件传递数据:通过props传递
    • 子组件给父组件传递数据:通过props传递,要求父组件宪哥子组件传递一个函数

细节:

  • defaultChecked只在第一次的时候有效果,之后不起作用,类似的还有defaultValue和value
  • checked单独用的话会报警告,而且相当于写死了,一般和onChange配合使用
  • 状态在哪里,操作状态的方法就在哪里

React ajax

前言

前置说明

  • React本身只关注于界面,并不包含发送ajax请求的代码(React不具备ajax功能)
  • 前端应用需要通过ajax请求与后台进行交互(JSON数据)(确实有这个需求)
  • react应用中需要集成第三方ajax库或者自己封装

React配置代理总结

方法一

在package.json中追加如下配置

"proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

方法二

  1. 第一步:创建代理配置文件

    src下创建配置文件:src/setupProxy.js
    
  2. 编写setupProxy.js配置具体代理规则:

    const proxy = require('http-proxy-middleware')
    
    module.exports = function(app) {
      app.use(
        proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
          target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
          changeOrigin: true, //控制服务器接收到的请求头中host字段的值
          /*
          	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
          	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
          	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
          */
          pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
        }),
        proxy('/api2', { 
          target: 'http://localhost:5001',
          changeOrigin: true,
          pathRewrite: {'^/api2': ''}
        })
      )
    }
    

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

消息订阅-发布机制

  • 先订阅,再发布
  • 适用于任意组件间通信
  • 要在组件的componentWillUnmount中取消订阅

fetch发送请求

try {
        const response = await fetch(`/api1/search/users?q=${keyword}`)
        const data = await response.json()
        console.log(response.data);
        PubSub.publish('peiqi', { isLoading: false, users: data.items })

        // console.log(data);
    } catch (error) {
        console.log('fail', error);
        PubSub.publish('peiqi', { isLoading: false, err: error.message })

    }

React路由

路由的理解

什么是路由

  1. 路由是一个映射关系(key:value)
  2. key为路径,value可能是function或者component

路由分类

  1. 后端路由
    • 理解:value是function,用来处理客户端提交的请求
    • 注册路由:router.get(path,function(req,res))
    • 工作过程:当node接到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
  2. 前端路由
    • 浏览器端路由,value是component,用于展示页面内容
    • 注册路由:
    • 工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件

路由的基本使用

  • 明确好界面中的导航区、展示区
  • 导航区的a标签改为Link标签 <Link to="/xxx">Demo</Link>
  • 展示区写Route标签进行路径的匹配 <Route path="/xxx" component={demo}>
  • <App>的最外侧包裹了一个<BrowserRouter>或者<HashRouter>

路由组件与一般组件

路由组件和一般组件的区别:

  1. 写法不同:
    • 一般组件:<Demo>
    • 路由组件:<Route path="/demo" component={Demo}>
  2. 存放位置不同:
    • 一般组件放在components文件夹中
    • 路由组件放在pages文件夹中
  3. 接收到的props不同:
    • 一般组件:写组件标签时传递了什么,就能收到什么
    • 路由组件:接收到三个固定的属性:
    • history
    • location
    • match

NavLink与封装NavLink

封装的<MyNavLink></MyNavLink>是一个一般组件

  • NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
  • 标签体内容是一个特殊的标签属性
  • 通过this.props.children可以获取标签体内容

Switch的使用

  • 通常情况下path和component是一一对应的关系
  • Switch可以提高路由匹配效率

解决多级路径刷新页面样式丢失的问题

  1. public/index.html中引入样式时不写.//(常用)
  2. public/index.html中引入样式时不写./%PUBLIC_URL%(常用)
  3. 使用HashRouter

路由的严格匹配和模糊匹配

  • 默认使用的是模糊匹配(简单记:输入的路径必须要包含匹配的路径,且顺序要一致)
  • 开启严格匹配: <Route exact={true} path="/about" component={About}>
  • 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

Redirect的使用

  • 一般在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
<Switch>
    <Route  path='/about' component={About}></Route>
    <Route  path='/home' component={Home}></Route>
    <Redirect to="/about"></Redirect>
</Switch>

嵌套路由

向路由组件传递参数

params参数

  • 路由链接(携带参数): <Link to="/demo/test/tom/18">详情</Link>
  • 注册路由(声明接收): <Route path="/demo/test/:name/:age" component={Test} />
  • 接收参数: const {name,age}=this.props.match.params

search参数

  • 路由链接(携带参数): <Link to="/demo/test?name=tom&age=18">详情</Link>
  • 注册路由(无需声明,正常注册即可): <Route path="/demo/test" component={Test} />
  • 接收参数: const {name,age}=this.props.location.search 备注:获取到的search是urlencoded编码字符串,需要借助query-string解析 import qs form "qurey-string"

state参数

  • 路由链接(携带参数): <Link to={{path:"/demo/test",state:{name:'tom',age:18}}}>详情</Link>
  • 注册路由(无需声明,正常注册即可): <Route path="/demo/test" component={Test} />
  • 接收参数: const {name,age}=this.props.location.state 备注:刷新也可以保留住参数

push 与replace

  • 默认是push模式
  • 修改为replace模式可以在Link或者NavLink等跳转组件中加入 : replace={true}或者直接简写:replace

编程式路由导航

借助this.props.history对象上的API对路由操作跳转、前进后退

this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()

BrowserRouter与HashRouter的区别

  1. 底层原理不一样:
    • BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
    • HashRouter使用的是URL的哈希值
  2. path表现形式不一样
    • BrowserRouter中路径中没有#,例如:localhost:3000/demo/test
    • HashRouter的路径中包含#例如:localhost:3000/#/demo/test
  3. 刷新后对路由state参数的影响
    • BrowserRouter没有任何影响,因为state保存在history对象中
    • HashRouter刷新后会导致路由state参数的丢失
  4. HashRouter可以用于解决一些路径错误相关的问题

React UI组件库

ant-design

redux

  • redux是一个专门用于做状态管理的js库(不是react插件库)
  • 它可以用在react ,angular,vue等项目中,但基本与react配合使用
  • 作用:集中式管理react应用中多个组件共享的状态

什么情况下需要使用redux?

  • 某个组件的状态,需要让其他组件可以随时拿到(共享)
  • 一个组件需要改变另一个组件的状态(通信)
  • 总体原则:能不用就不用,如果不用比较吃力才考虑使用

redux的三个核心概念

action

  • action是动作的对象
  • 包含两个属性:
    • type:标识属性,值为字符串,唯一,必要属性
    • data:数据类型,值类型任意,可选属性
    • 例如:{type:'ADD_STUDENT',data:{name:'tom',age:18}}

reducer

  • 用于初始化状态,加工状态
  • 加工时,根据旧的state和action,产生的state的纯函数

store

  • 将state,action,reducer联系在一起的对象

redux求和案例

精简版

  1. 去除Count组件自身的状态state
  2. src文件件下建立:
    • redux
    • store.js
    • count_render.js
  3. store.js:
    • 引入redux中的createStore函数,创建一个store
    • createStore调用时要传入一个为其服务的reducer
    • 暴露store对象
  4. count_reducer.js
    • reducer本质是一个函数,接收prestate,action,返回加工后的状态
    • reducer有两个作用:初始化状态、加工状态
    • reducer被第一次调用时,是state自动触发的,传递的preState是undefined
  5. 在index.js中检测store中状态的改变,一旦发生改变重新渲染<App/> 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写

完整版

新增文件:

  1. count_action.js专门用于创建action对象
  2. constant.js放置容易写错的type值

redux异步action版

明确:延迟的动作不想交给组件自身,想交给action 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回 具体编码:

  • npm i redux-thunk,并配置在store中
  • 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务
  • 异步任务有结果后,分发一个同步任务的action去真正操作数据

备注:异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action

react-redux的基本使用

明确两个概念:

  • UI组件:不能使用任何redux的API,只负责页面的呈现、交互等
  • 容器组件:负责和redux通信,将结果交给UI组件

如何创建一个容器组件——靠react-redux的connect函数

connect(mapStateToProps,mapDispatchToProps)(ui组件)

其中:

  • mapStateToProps:映射状态,返回值是一个对象
  • mapDispatchToProps:映射操作状态的方法,返回值是一个对象

备注:

  • 容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
  • mapDispatchToProps也可以是一个对象

react-redux数据共享版

定义一个Person组件,和Count组件通过redux共享数据 为Person组件编写:reducer,action,配置constant常量 重点:Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象 交给store的是总reducer,最后注意在组件中取出状态的时候,记得“取到位”

开发者工具的使用

安装:

npm i redux-devtools-extension

在store中配置:

import {composeWithDevTools} from 'redux-devtools-extension'
const store =createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)))

高阶函数和纯函数

纯函数

一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)

纯函数必须遵守以下约束:

  • 不得改写参数数据
  • 不会产生任何副作用,例如网络请求,输入和输出设备
  • 不能调用Date.now()或者Math.random()等不纯的方法

redux的reducer函数必须是一个纯函数

React 扩展

setState

setState更新状态的两种写法:

对象式setState

setState(stateChange,[callback])
  • stateChange为状态改变对象(该对象可以体现出状态的更改)
  • callback是可选的回调函数,它在状态更新完毕,界面也更新后(render调用后)才被调用

函数式setState

setState(updater,[callback])
  • updater为返回stateChange对象的函数
  • updater可以接收到state和props
  • callback是可选的回调函数,它在状态更新、界面也更新后(render调用后)才被调用

总结

使用原则:

  • 如果新状态不依赖原状态,使用对象式
  • 如果新状态依赖原状态,使用函数式

lazyLoad

Hooks

Hook是React16.8版本增加的新特性,新语法,可以让你在函数组件中使用state以及其他的React特性

三个常用的hook:

  • State hook:React.useState()
  • Effect hook: React.useEffect()
  • Ref Hook:React.useRef()

state Hook

state Hook让函数组件也可以有state状态,并进行状态数据的读写操作

语法:

const [xxx,setXxx]=React.useState(initValue)

useState说明:

  • 参数:第一次初始化指定的值在内部换成
  • 返回值:包含两个元素的数组,第一个为内部当前状态值,内部用其覆盖原来的状态值

setXxx()的两种写法: 第一种:

setXxx(newValue)
// 参数为非函数值,直接指定新的状态值,内部用其覆盖原来的值

第二种:

setXxx(value=>newValue)
参数为函数,接收原来的状态值,返回新的状态值,内部用其覆盖原来的状态值

effect Hook

Effect Hook可以让你在函数组件中执行副作用操作(用于模拟类组件中生命周期钩子)

React中的副作用操作:

  • 发送Ajax请求数据获取
  • 设置订阅
  • 起动定时器
  • 手动更改真是DOM

语法说明:

useEffect(()=>{
    // 在此执行任何副作用操作


    return()=>{
        // 在组件卸载前执行
        // 在此做一些收尾工作,比如清除定时器/取消订阅等
    }
},[stateValue]) //如果指定的是[],回调函数只会在第一次render后执行

可以把useEffect Hook可以看做三个钩子的组合:

  • componentDidMount()
  • componentDidUpdata()
  • componentWillUnmount()

Ref Hook

Ref Hook可以在函数组件中存储、查找组件内标签或者任意其他数据

语法:

const refContainer=useRef()

作用: 保存标签对象,功能和React.createRef()一样

Fragment

在jsx文件中如果有多个标签必须在这些标签外套一层div,这是jsx的规则 现在可以使用<Fragment></Fragment>来代替div了 jsx在编译成js的时候就会自动把Fragment去掉,页面中也不会出现div结构了

其实使用空标签<></>也能实现类似的效果

空标签和Fragment标签还是有一些区别的:

  • Fragment标签可以加一个key属性,空标签是不能加任何属性的
  • Fragment也只能加key属性,别的属性也加不了

content

一种组件间通信的方式(常用于组件件和后代组件间通信)

使用:

  1. 创建Context容器对象:
const XxxContext=React.createContext()
  1. 渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代传递数据
<xxxContext.Provider value={数据}>
子组件
</xxxContext.Provider>
  1. 后代组件读取数据:
// 第一种方式
static contextType=xxxConext
this.context

// 第二种方式
<xxxContext.Consumer>
{
    value=>(
        要显示的内容
    )
}
</xxxContext.Consumer>

组件优化

Component的两个问题:

  • 只要执行setState,即使不改变状态数据,组件也会重新render
  • 只当前组件重新render,就会自动重新render子组件,纵使子组件没有用到父组件的任何数据,效率低 问题的原因: Component中的shouldComponentUpdate()总是返回TRUE

期望中效率高的做法: 只有当组件中的state或者props数据发生变化时才重新render

解决两个问题的方法:

  1. 重写shouldComponentUpdate方法 比较新旧state或者props数据,如果有变化才返回TRUE,没有返回FALSE
  2. 使用PureComponent PureComponent重写了shouldComponentUpdate(),只有state或者props数据有变化才能返回TRUE 注意: 只是进行state和props数据的浅计较,如果只是数据对象内部数据变了,返回false 不要直接修改state数据,而是要产生新数据

项目中一般使用PureComponent来优化

render props

如何向组件内部动态的传入带有内容的结构?

  • Vue: 使用slot技术,也就是通过组件标签传入结构
  • React: 使用children props:通过组件标签传入结构 使用render props:通过组件标签属性传入结构,一般用render函数属性

child props

<A>
    <B>xxx</B>
</A>

{this.props.children}

问题:如果B组件需要A组件内的数据,做不到

render props

<A render={(data)=><C data={data}/>}>


// A组件
{this.props.render(内部state数据)}

// C组件:
读取A组价传入的数据显示{this.props.data}

错误边界

错误边界:用来捕获后代组件错误,渲染处备用页面

特点: 只能捕获组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式: getDerivedStateFromError配合componentDidCatch

static getDerivedStateFromError(error){
    console.log(error);

// 在render之前触发,返回新的state
    return {
        hasError:true
    }
}


componentDidCatch(error,info){
    // 统计错误页面,发送请求发送到后台去
    console.log(err,info)
}

组件间通信方式的总结

组件间的关系:

  • 父子关系
  • 兄弟关系
  • 祖孙关系

几种通信方式

props
  • children props
  • render props
消息订阅-发布

pub-sub,event等等

集中式管理

redux、dva等

conText:

生产者-消费者模式

比较好的搭配方式

  • 父子组件:props
  • 兄弟组件:消息订阅-发布,集中式管理
  • 祖孙组件:消息订阅-发布,集中式管理,conText

React Router 6

概述

React Router以三个不同的包发布到npm 上,他们分别是:

  • react-router:路由的核心库,提供了很多的组件,钩子
  • react-router-dom:包含react-router所有内容,并添加一些专门用于DOM的组件,例如:<BrowserRouter>
  • react-router-native:包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>

<BrowserRouter>

<BrowserRouter>用于包裹整个应用

代码:


import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'

ReactDOM.render(
    <BrowserRouter>
  {整个结构,通常为App组件}
    </BrowserRouter>
)

<Routes>

  • V6版本使用<Routes>代替原先的<Switch>
  • <Routes><Route>要配合使用,使用<Routes>包裹<Route>