React 框架

一、极速入门

1.基本概念

  • 1.什么是React
    • React 起源于 Facebook 内部项目,因为Facebook对市场上所有 JavaScript MVC 框架,都不满意, 就决定自己写一个框架,用来架设 Instagram 的网站
    • 来源: https://reactjs.org/blog/2013/06/05/why-react.html
  • 2.什么是框架
    • 框架是一个半成品,已经对基础的代码进行了封装并提供相应的API
    • 开发者在使用框架时,可以复用框架中封装好的代码,从而提高工作效率
    • 框架就是毛坯房, 已经帮助我们搭建好了基本的架子, 我们只需要拿过来根据
    • 我们自己的需求装修即可
  • 3.为什么要学习框架
    • 提升开发效率
  • 4.React核心思想
    • 数据驱动界面更新:只要数据发生了改变, 界面就会自动改变
    • 组件化开发:将网页拆分成一个个独立的组件来编写,然后再将编写好的组件,拼接成 一个完整的网页
  • 5.什么是虚拟DOM
    • 真实DOM:是相对于浏览器所渲染出来的真实 DOM 的
    • 虚拟DOM:就是使用JS对象来表示页面上真实的 DOM
<div id="name" title= "name">  // 真实的DOM
let obj = {                    // 虚拟DOM
  tagName: 'div',
  attrs:{
    id: "name" ,
    title: "name"
}

2.基本使用

  • 1.使用React的几种方式
    • 自行配置
      • https://zh-hans.reactjs.org/docs/add-react-to-a-website.html
    • 通过脚手架自动配置
      • https://zh-hans.reactjs.org/docs/create-a-new-react-app.html
  • 自行配置
  • 引入 react.development.v17.js 和 react-dom.development.v17.js
    • 可以将其先下载下来,然后再导入
    • 或者直接在线导入
<script src="https://unpkg.com/react@17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js" crossorigin>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--  引入react与react-dom -->
    <script src="react17/react.development.v17.js"></script>
    <script src="react17/react-dom.development.v17.js"></script>
</head>
<body>
    <div id="app"></div>
    <script>
        // 1.创建虚拟DOM
        // <div>知播渔</div>
        let message = '知播渔';
        // 参数解释:标签元素/组件 属性 内容
        let oDiv = React.createElement('div', null, message);

        // 2.将虚拟DOM转换成真实DOM
        // 参数解释:被渲染的虚拟DOM 要渲染到哪个元素中 渲染或更新完成后的回调函数
        ReactDOM.render(oDiv, document.getElementById("app"), ()=>{
            console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
        });
    </script>
</body>
</html>
  • 2.react.js 和 react-dom.js
    • react.js
      • 包含了React和React-Native所共同拥有的核心代码
      • 主要用于生成虚拟DOM
    • react-dom.js
      • 包含了针对不同平台渲染不同内容的核心代码
      • 主要用于将虚拟DOM转换为真实DOM
    • 简而言之
      • 利用react.js编写界面 (创建虚拟DOM)
      • 利用react-dom.js渲染界面 (创建真实DOM)
  • 3.React如何创建DOM元素
    • 在React中, 我们不能通过 HTML标签 直接创建DOM元素
    • 在React中, 我们必须先通过react.js创建虚拟DOM, 再通过react-dom.js渲染元素 (转换为真实DOM)
  • 4.如何通过react.js创建虚拟DOM
    • 通过React.createElement()方法
    • 该方法接收3个参数
      • 第一个参数: 需要创建的元素类型组件
      • 第二个参数: 被创建出来的元素拥有的属性
      • 第三个参数: 被创建出来的元素拥有的内容 (可以是多个)
    • createElement 注意点
      • 可以添加3个以上参数, 后续参数都会作为当前创建元素内容处理
  • 5.如何通过react-dom.js渲染虚拟DOM
    • 通过ReactDOM.render()方法
    • 该方法接收3个参数
      • 第一个参数: 被渲染的虚拟DOM
      • 第二个参数: 要渲染到哪个元素中
      • 第三个参数: 渲染或更新完成后的回调函数
    • 方法注意点
      • 多次渲染, 后渲染会覆盖先渲染
      • render 方法一次只能渲染一个元素/组件
  • 6.如何给元素添加监听
    • 给元素添加监听的本质就是给元素添加属性
    • 所以可以在createElement()的第二个参数中添加
    • 按钮
    • React.createElement('button', {onClick: btnClick}, '按钮');
    • 注意事项: 如果想给元素绑定事件, 那么事件名称必须是驼峰命名
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入react和react-dom -->
    <script src='../react17/react.development.v17.js'></script>
    <script src='../react17/react-dom.development.v17.js'></script>
</head>
<body>
    <div id="app"></div>
    <script>
        // 1.创建虚拟DOM
        let message = '知播渔';
        // <div>知播渔</div>
        let oDiv = React.createElement('div', null, message);
        // <button onclick='myfn'>按钮</button>
        let oBtn = React.createElement('button', null, '按钮');
        // 注意点: 如果想通过React绑定事件, 那么事件名称必须是驼峰命名
        let oRoot = React.createElement('div', { onClick: myFn }, oDiv, oBtn);
        
        // 2.将虚拟DOM转换成真实DOM
        ReactDOM.render(oRoot, document.getElementById('app'), () => {
            console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
        });

        function myFn() {
            message = 'www.it666.com';
            // 注意点: 默认情况下载React中, 修改完数据之后, 是不会自动更新界面的
            let oDiv = React.createElement('div', null, message);
            let oBtn = React.createElement('button', null, '按钮');
            let oRoot = React.createElement('div', { onClick: myFn }, oDiv, oBtn);
            ReactDOM.render(oRoot, document.getElementById('app'), () => {
                console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
            });
        }
    </script>
</body>
</html>

3.JSX

  • 1.通过createElement创建元素存在的问题
    • 如果结构比较简单还好, 但是如果结构比较复杂, 就比较难以下手
    • 所以大牛们就发明了JSX, 专门用来编写React中的页面结构体
  • 2.什么是JSX
    • JSX === JavaScript + X === (XML) === (eXtension)
    • JSX 是一个看起来很像 XML 的 JavaScript 语法扩展
  • 3.为什么要使用JSX
    • 使用JSX使得我们在React中编写页面结构更为简单、灵活
    • JSX 是类型安全的,在编译过程中就能发现错误
    • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化
    • 防止XSS注入攻击
    • 文档地址:https://zh-hans.reactjs.org/docs/introducing-jsx.html
  • 4.JSX本质
    • 浏览器只认识JS不认识JSX, 所以我们编写的JSX代码是无法在浏览器中执行的
    • 为了解决这个问题, 我们需要借助babel将JSX转换成JS, 也就是转换成React.createElement();
  • 5.如何在项目中将JSX转换成JS
    • 引入 babel.min.js
    • 在 script 标签上,添加 type="text/babel"
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入react和react-dom -->
    <script src='../react17/react.development.v17.js'></script>
    <script src='../react17/react-dom.development.v17.js'></script>
    <script src='../react17/babel.min.js'></script>
</head>
<body>
    <div id="app"></div>
    <script type="text/babel">
        let message = '知播渔';
        // 优化:将其封装为一个函数,后期直接调用
        function myRender(){
            // let oDiv = React.createElement('div', null, message);
            // let oBtn = React.createElement('button', null, '按钮');
            // let oRoot = React.createElement('div', { onClick: myFn }, oDiv, oBtn);
            // JSX 写法
            let oRoot = (
                <div>
                    <div>{message}</div>
                    <button onClick={myFn}>按钮</button>
                </div>
            )

            ReactDOM.render(oRoot, document.getElementById('app'), () => {
                console.log('已经将虚拟DOM转换成了真实DOM, 已经渲染到界面上了');
            });
        }
        myRender();
        
        function myFn() {
            message = 'www.it666.com';
            myRender();
        }
    </script>
</body>
</html>

4.组件的定义

  • 1.在React中如何定义组件
    • 在React中创建组件有两种方式
      • 第一种:通过ES6之前的构造函数来定义(无状态/数据组件)
      • 第二种:通过ES6开始的class类来定义(有状态/数据组件)
  • 2.如何通过ES5的构造函数来定义组件
    • 在构造函数中返回组件的结构即可
function Home() {
    return (
      <div>
          <div>{message}</div>
          <button onClick={btnClick}>按钮</button>
      </div>
    );
}
​
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入react和react-dom -->
    <script src='../react17/react.development.v17.js'></script>
    <script src='../react17/react-dom.development.v17.js'></script>
    <script src='../react17/babel.min.js'></script>
</head>
<body>
    <div id="app"></div>
    <script type="text/babel">
        let message = '知播渔';
        // 定义函数组件
        function Home(){
            return (
                <div>
                    <div>{message}</div>
                    <button onClick={myFn}>按钮</button>
                </div>
            )
        }
        // <Home/>:调用组件
        ReactDOM.render(<Home/>, document.getElementById('app'));

        function myFn() {
            message = 'www.it666.com';
            ReactDOM.render(<Home/>, document.getElementById('app'));
        }
    </script>
</body>
</html>
  • 3.如何通过ES6的class来定义组件
    • 定义一个类, 在这个类中实现 render方法, 在render方法中返回组件的结构即可
class Home extends React.Component{
    render(){
        return (
            <div>
                <div>{message}</div>
                <button onClick={btnClick}>按钮</button>
            </div>
        )
    }
}

5.组件的状态

  • 1.有状态组件和无状态组件
    • 首先需要明确的是, 组件中的状态(state)指的其实就是数据
      • 有状态组件:指的就是有自己数据的组件(逻辑组件)
      • 无状态组件:指的就是没有自己数据的组件(展示组件)
  • 2.如何定义自己的状态
    • 凡是继承于React.Component的组件, 默认都会从父类继承过来一个state属性
    • 这个state属性就是专门用来保存当前数据
    • 所以但凡是继承于React.Component的组件, 都是有状态组件
    • 所以但凡不是继承于React.Component的组件, 都是无状态组件
      • 所以类组件就是有状态组件
      • 所以函数组件就是无状态组件

6.this指向

  • 1.this指向问题
    • 在ES6之前, 方法中的this谁调用就是谁,并且还可以通过call/apply/bind方法修改this
    • 从ES6开始, 新增了箭头函数, 箭头函数没有自己的this,箭头函数中的this是函数外最近的那个this,并且由于箭头函数没有自己的this, 所以不能通过call/apply/bind方法修改this
  • 2.监听事件中的this
    • React内部在调用监听方法的时候, 默认会通过apply方法将监听方法的this修改为了undefined,所以在监听方法中无法通过this拿到当前组件的state. (undefined.state)
    • 如果想在监听方法中拿到当前组件的state, 那么就必须保证监听方法中的this就是当前实例,所以我们可以借助箭头函数的特性, 让React无法修改监听方法中的this, 让监听方法中的this就是当前实例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入react和react-dom -->
    <script src='../react17/react.development.v17.js'></script>
    <script src='../react17/react-dom.development.v17.js'></script>
    <script src='../react17/babel.min.js'></script>
</head>
<body>
    <div id="app"></div>
    <script type="text/babel">
        // let message = '知播渔';
        // 定义类组件
        class Home extends React.Component {
            constructor(){
                // 继承父类的方法与属性
                super();
                // stat:专门用于定义数据的
                this.stat = {
                    message: '知播渔'
                };
            }
            render() {
                return (
                    <div>
                        <div>{this.stat.message}</div>
                        <button onClick={this.myFn}>按钮</button>
                    </div>
                )
            }
            myFn = () => {
                // 注意点: React在调用监听方法的时候, 会通过apply修改监听方法的this
                //        所以在普通的方法中, 我们拿到的this是undefined,
                //        所以我们无法在普通的方法中通过this拿到当前组件的state
                // 解决方案:使用箭头函数,让React无法修改监听方法中的this
                // console.log(this);    // undefined
                this.stat.message = 'www.it666.com';
                ReactDOM.render(<Home />, document.getElementById('app'));
            }
        }
        // <Home/>:调用组件
        ReactDOM.render(<Home />, document.getElementById('app'));
    </script>
</body>
</html>

7.state属性

  • state属性注意点
    • 永远不要直接修改state,直接修改state并不会触发界面更新
    • 只有使用setState方法,修改state,才会触发界面更新
        myFn = () => {
            // 注意点:
            // 如果要修改state中的数据, 那么永远不要直接修改
            // 如果要修改state中的数据, 那么需要通过setState方法来修改
            // this.state.message = 'www.it666.com';
            // console.log(this.state.message);
            this.setState({
                message: 'www.it666.com'
            })
            //ReactDOM.render(<Home/>, document.getElementById('app'));
        }

案例:购物车

  

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入react和react-dom -->
    <script src='../react17/react.development.v17.js'></script>
    <script src='../react17/react-dom.development.v17.js'></script>
    <script src='../react17/babel.min.js'></script>
</head>
<body>
    <div id="app"></div>
    <!-- 需求:购物车  (增加 减少) -->
    <script type="text/babel">
        // 定义类组件
        class Counter extends React.Component{
            // 定义数据
            constructor(){
                super();
                this.state = {
                    count: 0
                };
            }
            // 搭建结构
            render(){
                return(
                    <div>
                        <button onClick={this.sub}>减少</button>
                        <span>{this.state.count}</span>
                        <button onClick={this.add}>增加</button>
                    </div>
                )
            }
            // 定义方法
            sub = () => {
                this.setState({
                    count: this.state.count - 1
                })
            }
            add = () => {
                this.setState({
                    count: this.state.count + 1
                })
            }
        }
        // 调用组件
        ReactDOM.render(<Counter/>, document.getElementById('app'));
    </script>
</body>
</html>

二、JSX 进阶

1.JSX注释

  • 在JSX中遇到 <> 会当做XML元素解析, 遇到 { } 会当做JS解析
  • 所以在JSX中不能使用HTML的注释
  • JSX代码用于定义网页的结构, 而网页的内容中必然会包含内容
  • 所以直接在JSX中使用单行注释//或多行注释/**/会被当做元素的内容处理
  • 正确方式
    • 告诉JSX, 注释的内容不是元素的内容即可
    • 只需要将注释的内容写到 { } 中, JSX就会把注释的内容当做是JS来处理
    • {/* 注释 */}

2.JSX绑定内容

  • 在JSX中只要看到 { } 就会当做JS解析(执行里面的JS代码)
  • 所以无论是绑定属性,还是绑定类名,还是绑定样式, 只需要将字符串改为 {}
  • 然后再通过JS动态获取, 动态绑定即可
  • 2.1 绑定普通属性
    • 过去怎么绑定, 现在就怎么绑定
      • 我是段落

      • 我是段落

  • 2.2 绑定类名(class)
    • 由于JSX本质是转换成JS代码, 而在JS中class有特殊含义, 所以不能使用
    • 同理可证, 但凡是属性名称是JS关键字的都不能直接使用
    • className="active">绑定类名

  • 2.3 绑定样式(style)
    • 由于样式是键值对形式的, 所以在JSX中如果想要动态绑定样式
    • 必须将样式放到一个对象中, 并且所有以-连接的样式名称都要转换成驼峰命名
      • {color:'red', fontSize:'50px'}}>绑定样式

    // 定义结构(JSX)
    render() {
        return (
            <div>
                {/*1.对于普通属性而言, 过去怎么绑定, 现在就怎么绑定*/}
                <p id="box">{this.state.message}</p>
                <p title={this.state.message}>{this.state.message}</p>
                {/*2.如果想通过JSX绑定类名, 那么不能直接通过class来绑定
                        JSX本质是转换成JS代码, 而在JS代码中class是一个关键字
                        所以不能直接使用class来绑定类名*/}
                {/*3.所以以后但凡名称是JS关键字的属性, 都不能直接绑定*/}
                <p className="active">{this.state.message}</p>
                {/*4.如果想通过JSX绑定样式, 那么不能像过去一样编写
                        必须通过对象的形式来绑定*/}
                {/*5.在绑定样式的时候, 如果过去在原生中是通过-连接的, 那么就必须转换成驼峰命名*/}
                <p style={{ color: 'red', fontSize: '100px' }}>{this.state.message}</p>
            </div>
        )
    }

3.JSX嵌入内容

  • 1.JSX嵌入表达式
    • 只要是合法的表达式, 都可以嵌入到JSX中
  • 2.要想显示 truefalseundefined必须先转换成 字符串
    render(){
        return (
            <div>
                {/*1.任何合法的JS表达式都可以嵌入到{}中*/}
                <p>{this.state.flag ? '为真' : '为假'}</p>
                {/*2.以下嵌入的内容不会被显示出来 [] true false null undefined*/}
                <p>{[]}</p>
                <p>{true}</p>
                <p>{false}</p>
                <p>{null}</p>
                <p>{undefined}</p>
                {/*3.如果想显示上面的这些内容, 那么就必须先转换成字符串
                        但是对于空数组来说, 哪怕转换成了字符串, 也不能显示*/}
                <p>{[].toString()}</p>
                <p>{true + ''}</p>
                <p>{false + ''}</p>
                <p>{null + ''}</p>
                <p>{undefined + ''}</p>
                {/*4.除了上述内容以外, 其它的内容都可以正常显示*/}
                <p>我是段落</p>
                <p>{this.state.message}</p>
            </div>
        )
    }

4.JSX灵活性

  • JSX使我们在JS中,拥有了直接编写XML代码的能力
  • 所以在JS中能干的事,在JSX中都能干

需求:通过按钮,控制界面上p标签的显示和隐藏

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 引入react和react-dom -->
    <script src='react17/react.development.v17.js'></script>
    <script src='react17/react-dom.development.v17.js'></script>
    <script src='react17/babel.min.js'></script>
</head>
<body>
    <div id="app"></div>
    <script type="text/babel">
    // 需求:通过按钮控制界面上p标签的显示和隐藏
    class Home extends React.Component{
        constructor(){
            super();
            this.state = {
                flag: true
            }
        }
        render(){
            return(
                <div>
                    {/*和Vue中的v-show指令很像*/}
                    {/* <p style={{display: this.state.flag?'block':'none'}}>我是段落</p> */}
                    {/*和Vue中的v-if指令很像*/}
                    {/* &&表示当第一个参数取值为真时,才会执行第二个参数*/}
                    {this.state.flag && <p>我是段落</p>}
                    <button onClick={this.myFn}>按钮</button>
                </div>
            )
        }
        myFn = ()=>{
            this.setState({
                flag: !this.state.flag
            })
        }
    }
    ReactDOM.render(<Home/>, document.getElementById('app'));
    </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>
    <!-- 引入react和react-dom -->
    <script src='react17/react.development.v17.js'></script>
    <script src='react17/react-dom.development.v17.js'></script>
    <script src='react17/babel.min.js'></script>
</head>
<body>
<div id="app"></div>
    <script type="text/babel">
        // 需求: 渲染列表元素
        class Home extends React.Component {
            constructor() {
                super();
                this.state = {
                    names: ['鲁班', '虞姬', '黄忠']
                }
            }
            render() {
                const { names } = this.state;
                // const nameList = [];
                // nameList.push(<li>{this.state.names[0]}</li>);
                // nameList.push(<li>{this.state.names[1]}</li>);
                // nameList.push(<li>{this.state.names[2]}</li>);
                // nameList.push(<li>{this.state.names[3]}</li>);

                // const nameList = [];
                // for(let i = 0, j = names.length; i < j; i++){
                //     const li = <li>{names[i]}</li>
                //     nameList.push(li);
                // }

                // const nameList = names.map(name=>{
                //     return<li>{name}</li>
                // });
                return (
                    <div>
                        {/* 方式1 */}
                        {/*
                        <ul>
                            <li>{this.state.names[0]}</li>
                            <li>{this.state.names[1]}</li>
                            <li>{this.state.names[2]}</li>
                        </ul>
                        */}

                        {/* 方式2 */}
                        {/*
                        <ul>{nameList}</ul>
                        */}

                        {/* 方式3 */}
                        <ul>{
                            names.map(name => {
                                return <li>{name}</li>
                            })
                        }</ul>
                    </div>
                )
            }
        }
        ReactDOM.render(<Home />, document.getElementById('app'));
    </script>
</body>
</html>

5.JSX书写规范

  • JSX的顶层 只能有一个根元素
  • JSX中的标签可以是单标签也可以是双标签, 但如果是单标签必须闭合( /> )
  • 如果JSX中包含多个元素, 建议使用 () 包裹

6.JSX绑定事件

  • JSX中绑定事件必须使用 驼峰命名
    • onClick={this.btnClick}>按钮
  • 事件监听方法中的 this
    • 默认情况下React在调用事件监听方法的时候, 是通过apply来调用的
    • 并且在调用的时候,将监听方法中的this修改为了undefined
    • 所以默认情况下,我们是无法在监听方法中使用this的
  • 监听方法this处理
    • 1.箭头函数
    • 2.通过添加监听方法的时候, 手动通过bind修改监听方法中的this
      • 按钮
    • 3.通过在构造函数中,  手动通过bind修改监听方法中的this
      • this.myClick = this.btnClick.bind(this);
    • 4.手动绑定一个箭头函数, 然后再箭头函数的函数体中手动调用监听方法 (企业推荐)
      • () => { this.btnClick() }}>按钮
      • 因为箭头函数中的this, 就是当前的实例对象
      • 因为监听方法并不是React调用的, 而是我们在箭头函数中手动调用的
      • 因为普通的方法, 默认情况下谁调用就是谁
    class Home extends React.Component {
        constructor() {
            super();
            this.state = {
                message: '知播渔'
            }
            this.myClick = this.btnClick.bind(this);
        }
        render() {
            return (
                <div>
                    <div>{this.state.message}</div>
                    {/*方式2*/}
                    {/*
                    <button onClick={this.btnClick.bind(this)}>按钮</button>
                    */}
                    {/*方式3*/}
                    {/*
                    <button onClick={this.myClick}>按钮</button>
                    */}
                    {/*方式4: 优势就在于传参很方便*/}
                    <button onClick={() => { this.btnClick(1,2) }}>按钮</button>
                </div>
            )
        }
        btnClick(a,b) {
            console.log(this);
            console.log(a,b);
        }
        
        // 方式1:箭头函数
        // btnClick =()=>{
        //     console.log(this);
        // }

    }

7.JSX事件对象

  • 1.JSX事件参数
    • 和原生JS一样, React在执行监听方法会传递一个事件对象给我们
    • 但是React传递给我们的并不是原生的事件对象, 而是一个React自己合成的事件对象
  • 2.什么是合成事件
    • 合成事件是React在浏览器事件基础上做的一层包装,基本上有着和浏览器的原生事件有相同的接口,也能够进行 stopPropagation() 和 preventDefault(),并且合成事件在所有浏览器中的工作方式相同
    • 如果由于某种原因需要浏览器的原生事件,则能够简单的通过nativeEvent属性就能够获取
    render() {
        return (
            <div>
                <div>{this.state.message}</div>
                <button onClick={(e) => { console.log(e) }}>按钮</button>
                <button onClick={(e) => { console.log(e.nativeEvent) }}>按钮</button>
            </div>
        )
    }

三、组件核心

1.脚手架

  • 1.什么是脚手架
    • 脚手架是一种能快速帮助我们生成项目结构和依赖工具
    • 每个项目完成的效果不同,但是它们的基本工程化结构是相似的
    • 既然相似,就没有必要每次都从零开始搭建,完全可以使用一些工具,帮助我们生成基本的项目模板
    • 那么这个帮助我们生成项目模板的工具我们就称之为'脚手架'
  • 2.什么是create-react-app
    • create-react-app 就是 React的脚手架工具
    • 它可以快速的帮我们生成一套利用webpack管理React的项目模板

2.利用脚手架构建react项目

  • 1.如何安装和使用 create-react-app
    • npm install -g create-react-app     #安装脚手架工具
    • create-react-app 项目名称            #创建react项目
    • cd 项目名称       #进入项目
    • npm start           #启动项目
  • 2.注意点
    • 如果我们是通过create-react-app来创建React项目
    • 那么在指定项目名称的时候, 项目的名称只能是英文, 并且只能是小写字母
    • 如果出现了多个单词, 那么我们需要通过 _ 或者 - 来连接
    • myName  ->  my_name 或  my-name
  • 3.注意事项
    • 第一次运行项目的时候大概率会出现一个错误, 会出现本地webpack的版本和项目    依赖的webpack版本不同的错误
    • 如果遇到了这个错误, 我们就需要先通过 npm uninstall webapck 卸载掉本地的webpack
    • 再通过 npm install -g webpack@xx.xx.xx 安装和项目相同版本的webpack即可
  • 4.暴露 webapck 配置
    • npm run eject

3.项目结构解读

项目名称

├── README.md           // 说明文档

├── node_modules        // 依赖包

├── package.json        // 对应用程序的描述

├── .gitignore          // 不通过git管理的文件描述

├── public

│   ├── favicon.ico    // 应用程序收藏图标

│   ├── index.html     // 应用程序入口

│   ├── logo192.png    // manifest中PWA使用的图片

│   ├── logo512.png    // manifest中PWA使用的图片

│   ├── manifest.json  // PWA相关配置

│   └── robots.txt     // 搜索引擎爬虫权限描述文件

└── src

    ├── App.css         // 组件相关样式

    ├── App.js          // 组件相关代码

    ├── App.test.js     // 组件测试文件

    ├── index.css       // 全局样式

    ├── index.js        // 全局入口文件

    ├── logo.svg        // React图标

    └── serviceWorker.js   // 注册PWA相关代码

4.组件的定义

  • 1.什么是组件化开发
    • 组件化开发其实就是分而治之的思想
    • 我们可以将一个完整的界面拆分成多个小的界面每个小的界面就是一个组件
    • 每个组件管理自己的状态(数据)和业务逻辑
    • 这样做的既可以提高每个组件的复用性, 又可能降低每个组件的开发难度
  • 2.如何定义组件
    • 通过构造函数定义(函数组件, 无状态组件, 展示型组件)
    • 通过定义(类组件, 有状态组件, 逻辑组件)

如何在构建好的 react 项目中,编写代码?

src/App.js

import React from 'react';
import Header from './Components/Header'
import Main from './Components/Main'
import Footer from './Components/Footer'

// 定义App组件
class App extends React.Component{
    render(){
        return(
            <div>
                {/* 使用组件 */}
                <Header />
                <Main />
                <Footer />
            </div>
        )
    }
}

// 暴露App组件
export default App;

src/index.js

import ReactDOM from 'react-dom';
import React from 'react';
import App from './App';

// 渲染组件
ReactDOM.render(<App/>, document.getElementById('root'))

 Components/Header.js    (Compents 专门用于定义组件)

import React from 'react';
// 导入样式
import './Header.css';

function Header() {
    return (
        <div className={'header'}>我是头部2</div>
    )
}

export default Header;

 Components/Header.css

.header{
    background: red;
}

 Components/Main.js

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

function Main() {
    return (
        <div className={'main'}>我是中间2</div>
    )
}
export default Main;

 Components/Main.css

.main{
    background: green;
}

  Components/Footer.js 

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

function Footer() {
    return (
        <div className={'footer'}>我是底部2</div>
    )
}
export default Footer;

 Components/Footer.css

.footer{
    background: blue;
}

5.父子组件通讯

  • 父组件 如何给 子组件 传递参数
    • 父组件在调用子组件的时候,就可以给子组件传递数据

  • 第一种:子组件为函数组件

  • 1.如何在子组件设置参数默认值
    • 通过 defaultProps
// 设置参数默认值
Header.defaultProps = {
    name: '知播渔',
    age: 666
}

  • 2.如何在子组件中校验参数类型
    • 通过 propTypes
    • 首先需要安装prop-types: npm install prop-types
Header.propTypes = {
    name: ReactTypes.string,
    age: ReactTypes.number
}

  • 第二种:子组件为类组件

  • 改进:super(props);

6.子父组件通讯

  • 子组件 如何给 父组件 传递参数
    • 1.父组件传递一个方法给子组件
    • 2.子组件在调用父组件传递过来的方法
    • 3.只组件在调用这个方法的时候, 就可以通过方法传参的形式给父组件传递数据或者方法

7.跨组件通讯

  • 可以一层一层的,进行数据传递
class Son extends React.Component{
    // 3.子组件接收父组件传递过来的数据
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div>
                <p>我是儿子</p>
                <p>{this.props.name}</p>
                <button onClick={()=>{this.btnClick()}}>儿子按钮</button>
            </div>
        )
    }
    btnClick(){
        // 儿子组件给爷爷组件传递数据:执行回调函数
        this.props.appFn(18);
    }
}
class Father extends React.Component{
    // 爸爸组件接收数据
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div>
                <p>我是爸爸</p>
                {/* 2.父组件给子组件继续传递数据 */}
                <Son name={this.props.name} appFn={this.props.appFn}/>
            </div>
        )
    }
}
class App extends React.Component{
    render(){
        return (
            <div>
                {/* 爷爷组件给子组件传递数据:一层一层的进行传递 */}
                {/* 1.爷爷组件给父组件传递数据name */}
                <Father name={'lnj'} appFn={this.myFn.bind(this)}/>
            </div>
        )
    }
    myFn(age){
        console.log(age);
    }
}

兄弟组件:

class A extends React.Component{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div>
                <button onClick={()=>{this.btnClick()}}>A按钮</button>
            </div>
        )
    }
    btnClick(){
        {/* 1.子组件:A给父组件传递数据 */ }
        this.props.appFn('lnj');
    }
}
class B extends React.Component{
    // 接收父组件传递过来的数据
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div>
                <p>{this.props.name}</p>
            </div>
        )
    }
}
class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            name:''
        }
    }
    render(){
        return (
            <div>
                <A appFn={this.myFn.bind(this)}/>
                {/* 2.父组件App给子组件B传递数据 */}
                <B name={this.state.name}/>
            </div>
        )
    }
    myFn(name){
        // console.log(name);
        this.setState({
            name: name
        })
    }
}
  • 弊端:如果传递数据层次太深, 一层一层的传递比较麻烦, 所以React也提供了其它的解决方案
    • 1.通过context上下文传递
    • 2.通过Redux传递  (相当于Vuex)
    • 3.通过Hooks传递  (相当牛X)

(1).如何通过 context 上下文来传递数据

  • 方式1 
    • 1.调用创建上下文的方法, 只要我们调用了创建上下文的方法, 这个方法就会给我们返回  两个容器组件
      • 生产者容器组件(Provider) / 消费者容器组件(Consumer)
    • 2.只要拿到了这两个容器组件, 那么我们就可以通过这两个容器组件从祖先传递数据给   所有的后代了
    • 3.首先在祖先组件中利用 '生产者容器组件' 包裹后代组件
    • 4.然后在后代组件中利用 '消费者容器组件' 获取祖先组件传递过来的数据即可
import React from 'react';

// 1.创建一个上下文对象
const AppContext = React.createContext({});
// 2.从上下文对象中获取容器组件
const { Provider, Consumer } = AppContext;

class Son extends React.Component{
    render(){
        return(
            // 4.通过Consumer消费者容器组件,消费数据
            <Consumer>
                {
                    (value)=>{
                        return(
                            <div>
                                <p>Son</p>
                                <p>{value.name}</p>
                                <p>{value.age}</p>
                            </div>
                        )
                    }
                }
            </Consumer>
        )
    }
}

class Father extends React.Component {
    render() {
        return (
            <div>
                <p>Father</p>
                <Son/>
            </div>
        )
    }
}

// 跨组件通讯:爷爷组件App如何给孙子组件Son传递参数?
// context上下文
// Provider: 生产者容器组件, 专门用于负责生产数据
// Consumer: 消费者容器组件, 专门用于消费生产者容器组件生产的数据的
// 容器组件 : 专门用于包裹其它组件的组件, 我们就称之为容器组件
class App extends React.Component {
    render() {
        return (
            <div>
                <p>App</p>
                {/* 3.在Provider生产者容器组件中,通过value产生数据 */}
                <Provider value={{name:'lnj', age:19}}>
                    <Father/>
                </Provider>
            </div>
        )
    }
}

export default App;
  • 方式2
    • 1.创建上下文对象并生产数据
    • 2.指定当前组件的上下文
    • 3.通过 context 消费数据
// 1.创建一个上下文对象,并生产数据
const AppContext = React.createContext({
    name:'知播渔',
    age: 666
});
class Son extends React.Component{
    render(){
        return (
            <div>
                {/*3.从当前组件的上下文中消费数据*/}
                <p>{this.context.name}</p>
                <p>{this.context.age}</p>
            </div>
        )
    }
}
// 2.指定当前组件的上下文
Son.contextType = AppContext;

class Father extends React.Component{
    render(){
        return (
            <div>
                <p>{this.context.name}</p>
                <p>{this.context.age}</p>
                <Son></Son>
            </div>
        )
    }
}
Father.contextType = AppContext;
class App extends React.Component{
    render(){
        return (
            <div>
                <Father></Father>
            </div>
        )
    }
}
  • 注意:可以有多个生产者容器与消费者容器,但是如果有多个,则不能使用第二种方式
  • context 方式的弊端
    • 1.通过context我们已经能够实现跨组件通讯,但是只能实现从上往下传递
    • 不能实现从下往上传递或者同级之间传递
    • 子父组件之间通讯, 是通过回调函数的方式
    • 兄弟组件之间通讯, 也是通过父组件, 通过回调函数的方式
    • 2.但是如果通过回调函数, 传统的方式我们需要一层一层的传递, 比较复杂
    • 所以我们可以借助一个第三方库(events)来实现跨组件事件通讯

(2).如何使用 events 库实现跨组件通讯

  • 1.安装events库:npm install events
  • 2.创建EventEmitter对象:eventBus对象;
  • 3.监听事件:eventBus.addListener("事件名称", 监听函数);
  • 4.移除事件:eventBus.removeListener("事件名称", 监听函数);
  • 5.触发事件:eventBus.emit("事件名称", 参数列表);
import React from 'react';
import { EventEmitter } from 'events';

// 跨组件通讯:孙子组件Son如何给爷爷组件App传递数据?
// events 
// 监听事件:eventBus.addListener("事件名称", 监听函数) ;
// 移除事件:eventBus.removeListener("事件名称", 监听函数) ;
// 触发事件:eventBus.emit("事件名称", 参数列表);

// 1.在全局创建一个全局的事件管理器对象
const eventBus = new EventEmitter();

class Son extends React.Component{
    render(){
        return(
            <div>
                <p>Son</p>
                <button onClick={()=>{this.btnClick()}}>按钮</button>
            </div>
        )
    }
    btnClick(){
        // 4.触发事件,并通过参数给爷爷组件App传递数据
        eventBus.emit('say', 'lnj', 18);
    }
}

class Father extends React.Component {
    render() {
        return (
            <div>
                <p>Father</p>
                <Son/>
            </div>
        )
    }
}

class App extends React.Component {
    // 2.监听事件
    // componentDidMount:当组件被渲染到界面时,React会自动调用该方法
    componentDidMount(){
        eventBus.addListener('say',this.appFn.bin(this));
    }
    // 3.移除事件
    // componentWillUnmount:当组件被卸载时,React会自动调用该方法
    componentWillUnmount(){
        eventBus.removeListener('say', this.appFn.bin(this));
    }
    // 5.爷爷组件接收数据
    appFn(name, age){
        console.log(name, age);
    }
    render() {
        return (
            <div>
                <p>App</p>
                <Father/>
            </div>
        )
    }
}

export default App;

state 与 setState:

  • 1.props 和 state 的区别
    • props 和 state 都是用来存储数据
      • props:存储的是父组件传递过来的数据(props只读)
      • state:存储的是自己的数据(state可读可写)
  • 2.setState是同步的还是异步的
    • 在组件生命周期或React合成事件中,setState是异步;(异步是为了优化性能)
    • 在setTimeout或者原生dom事件中,setState是同步;
  • 3.如何拿到更新之后的数据
    • setState方法其实可以接收两个参数
    • 通过setState方法的第二个参数, 通过回调函数拿到
        this.setState({
            age: 111
        }, ()=>{
            console.log('回调函数中', this.state.age);
        });
  • 4.setState 是如何给 state 赋值的
    • 通过 Object.assign()
        let oldObj = {name:'lnj', age:18};
        let newObj = {age: 666};
        /*
        {name:'lnj', age:666}
        * */
        let obj = Object.assign({}, oldObj, newObj);
        console.log(obj);
  • 5.State合并现象
    • 因为 setState 会收集一段时间内所有的修改再更新界面
    • 所以就出现了 State 合并现象
    • 如何解决?
        this.setState({
            age: this.state.age + 1
        }, ()=>{
            this.setState({
                age: this.state.age + 1
            }, ()=>{
                this.setState({
                    age: this.state.age + 1
                });
            });
        });
         */
        this.setState((preState, props)=>{
            return {age: preState.age + 1}
        })
        this.setState((preState, props)=>{
            return {age: preState.age + 1}
        })
        this.setState((preState, props)=>{
            return {age: preState.age + 1}
        })

四、深入组件

1.组件的生命周期

  • 1.什么是生命周期
    • 事物从生到死的过程, 我们称之为生命周期
  • 2.什么是生命周期方法
    • 事物在从生到死过程中, 在特定时间节点调用的方法, 我们称之为生命周期方法
  • 3.React组件生命周期方法
    • 组件从生到死的过程, 在在特定的时间节点调用的方法, 我们称之为组件的生命周期方法
      • constructor函数:组件被创建的时候, 就会调用
      • render函数:渲染组件的时候, 就会调用
      • componentDidMount函数:组件已经挂载到DOM上时(渲染已经完成),就会回调
      • componentDidUpdate函数:组件已经发生了更新时(重新渲染已完成),就会回调
      • componentWillUnmount函数:组件即将被移除时,就会回调

2.组件的生命周期方法

常用的组件生命周期方法:

  • 1. constructor 生命周期方法中做什么
    • 通过 props 接收父组件传递过来的数据
    • 通过 this.state 初始化内部的数据
    • 通过 bind 为事件绑定实例(this)
      • this.myClick = this.btnClick.bind(this);
  • 2. render 生命周期方法中做什么
    • 返回组件的网页结构
  • 3. componentDidMount 生命周期方法中做什么
    • 依赖于DOM的操作可以在这里进行
    • 在此处发送网络请求就最好的地方(官方建议)
    • 可以在此处添加一些订阅(会在componentWillUnmount取消订阅)
  • 4. componentDidUpdate 生命周期方法中做什么
    • 可以在此对更新之后的组件进行操作
  • 5. componentWillUnmount 生命周期方法中做什么
    • 在此方法中执行必要的清理操作;例如,清除timer定时器,取消网络请求或清除
    • 在 componentDidMount() 中创建的订阅等

不常用的组件生命周期方法:

  • 1. getDerivedStateFromProps:将props中的数据映射到state中
  • 2. shouldComponentUpdate:更新时,决定是否更新(true更新 flase不更新)
  • 3. getSnapshotBeforeUpdate:获取更新之前的数据

3.组件的渲染流程与更新流程

(1).React 渲染流程

  • 1.执行render方法
<div>
    <div><p>我是段落</p></div>
    <div><span>我是span</span></div>
</div>
  • 2.将JSX转换成createElement
React.createElement("div", null,
    React.createElement("div", null,
        React.createElement("p", null, "我是段落")),
    React.createElement("div", null,
        React.createElement("span", null, "我是span"))
);
  • 3.执行createElement创建虚拟DOM, 得到虚拟DOM树
{
 targetName: 'div',
 children:[
    {
     targetName: 'div',
     children:[
        {
         targetName: 'p'
        }
     ]
    },
    {
     targetName: 'div',
     children:[
        {
         targetName: 'span'
        }
     ]
    }
 ]
}
  • 4.根据虚拟DOM树在界面上生成真实DOM

(2).React 更新流程

  • 1.props/state发生改变
  • 2.render方法重新执行
  • 3.将JSX转换成createElement
  • 4.利用createElement重新生成新的虚拟DOM树
  • 5.新旧虚拟DOM通过diff算法进行比较
  • 6.每发现一个不同就生成一个mutation
  • 7.根据mutation更新真实DOM

4.React-Diff 算法

  • 只会比较同层元素
  • 只会比较同位置元素(默认)
  • 在比较过程中
    • 同类型元素做修改
    • 不同类型元素重新创建

5.列表渲染优化

  • 1.列表渲染优化
    • 由于diff算法在比较的时候,默认情况下只会进行同层同位置的比较
    • 所以在渲染列表时可能会存在性能问题
  • 2.如何让diff算法,递归比较同层所有元素
    • 给列表元素添加 key ,告诉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>
    <!-- 引入react和react-dom -->
    <script src='react17/react.development.v17.js'></script>
    <script src='react17/react-dom.development.v17.js'></script>
    <script src='react17/babel.min.js'></script>
</head>
<body>
    <div id="app"></div>
    <script type="text/babel">
        // 需求: 渲染列表元素
        class Home extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    heroList: ['鲁班', '虞姬', '黄忠']
                }
            }
            // 1.列表渲染优化
            // 由于diff算法在比较的时候默认情况下只会进行同层同位置的比较
            // 所以在渲染列表时可能会存在性能问题

            // 2.如何让diff算法递归比较同层所有元素
            // 给列表元素添加key, 告诉React除了和同层同位置比, 还需要和同层其它位置比
            render() {
                return (
                    <div>
                        <ul>{
                            this.state.heroList.map(name => {
                                // key={name}:提升性能 
                                return <li key={name}>{name}</li>
                            })
                        }</ul>
                        <button onClick={() => { this.btnClick() }}>按钮</button>
                    </div>
                )
            }
            btnClick() {
                this.setState({
                    // heroList: [...this.state.heroList, '阿珂']
                    heroList: ['阿珂', ...this.state.heroList]
                })
            }
        }
        ReactDOM.render(<Home />, document.getElementById('app'));
    </script>
</body>
</html>
  • key注意点
    • 如果列表中出现相同的key, 所以我们必须保证列表中key取值唯一

6.组件性能优化

  • 1.嵌套组件的render调用
    • 默认情况下, 只要父组件render被调用, 那么所有后代组件的render也会被调用
  • 2.当前存在的问题
    • 如果我们只修改了父组件的数据, 并没有修改子组件的数据, 并且子组件中也没有用到    父组件中的数据
    • 那么子组件还是会重新渲染, 子组件的render方法还是会重新执行, 这样就带来了性能问题
  • 3.解决方案
    • 3.1 类组件
      • 方式1
      • 在子组件中,添加 shouldComponentUpdate 方法,判断自身的 state 数据是否      发生改变
class Home extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            age : 18
        }
    }
    shouldComponentUpdate(nextProps, nextState, nextContext) {
        // return true;
        // return false;
        if(this.state.age !== nextState.age){
            return true;
        }
        return false;
    }

    render(){
        console.log('Home-render被调用');
        return (
            <div>
                <p>{this.state.age}</p>
            </div>
        )
    }
}
class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            name : 'lnj'
        }
    }
    render(){
        console.log('App-render被调用');
        return (
            <div>
                <p>{this.state.name}</p>
                <button onClick={()=>{this.btnClick()}}>APP按钮</button>
                <Home/>
            </div>
        )
    }
    btnClick(){
        this.setState({
            name:'知播渔'
        })
    }
}
  • 存在的问题(shouldComponentUpdate)
    • 所有需要优化子组件都需要实现这个方法, 但这个方法并没有技术含量
  • 方式2
  • 让类组件 继承 于 React.PureComponent , 让React自动帮我们实现
  • 3.2 函数组件
    • 对于函数组件来说
      • 没有继承关系
      • 没有生命周期方法
    • 对于类组件来说
      • 我们可以通过实现shouldComponentUpdate方法
      • 或者继承于PureComponent来解决性能优化问题,
      • 但是对于函数式组件, 是没有生命周期的,是没有继承关系的
    • 那么在函数式组件中如何解决性能优化问题呢
      • 通过 React.memo()  高阶函数
        • React.memo() 会返回一个优化后的组件给我们
/*
function Home() {
    console.log('Home-render被调用');
    return (
        <div>
            <p>Home</p>
        </div>
    )
}
*/
const PurHome = React.memo(function() {
    console.log('Home-render被调用');
    return (
        <div>
            <p>Home</p>
        </div>
    )
});
  • state 注意点
    • 永远不要直接修改state中的数据
    • 如果要修改state中的数据, 必须通过 setState 传递一个新的值

7.React 获取元素

(1).React 获取元素的几种方式

  • 1.传统方式(在React中及其不推荐)
    • let oP = document.getElementById('box');
  • 2.字符串(即将被废弃, 也不推荐)
    • let oP = this.refs.box;
  • 3.对象(推荐)
    • let oP = this.oPRef.current;
  • 4.回调函数(推荐)
    • let oP = this.oPRef;
import React from 'react';

class App extends React.PureComponent {
    constructor(props) {
        super(props);
        // this.oPRef = React.createRef();
        this.oPRef = null;
    }
    render() {
        console.log('App-render被调用');
        return (
            <div>
                {/* <p id={'box'}>我是段落</p> */}

                {/* <p ref={'box'}>我是段落</p> */}

                {/* <p ref={this.oPRef}>我是段落</p> */}

                <p ref={(arg) => { this.oPRef = arg }}>我是段落</p>

                <button onClick={() => { this.btnClick() }}>APP按钮</button>
            </div>
        )
    }
    btnClick() {
        // 1.传统方式(在React中及其不推荐)
        // let oP = document.getElementById('box');

        // 2.通过ref='字符串' / this.refs.字符串 (通过字符串的方式即将被废弃, 也不推荐)
        // let oP = this.refs.box;

        // 3.通过createRef()创建一个对象, 然后将这个对象传递给ref (推荐)
        // let oP = this.oPRef.current;

        // 4.通过传递一个回调函数, 然后保存回调函数参数的方式(推荐)
        let oP = this.oPRef;
        oP.innerHTML = 'www.it666.com';
        console.log(oP);
    }
}

export default App;
  • 注意点
    • 获取原生元素, 拿到的是元素本身
    • 获取类组件元素, 拿到的是组件实例对象
    • 获取函数组件元素, 拿不到任何内容
import React from 'react';

// 类组件
class Home extends React.PureComponent {
    render() {
        return (
            <div>Home</div>
        )
    }
}
// 函数组件
function About() {
    return (
        <div>About</div>
    )
}
class App extends React.PureComponent {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    render() {
        return (
            <div>
                {/* <p ref={this.myRef}>我是段落</p> */}
                {/* <Home ref={this.myRef}/> */}
                <About ref={this.myRef} />
                <button onClick={() => { this.btnClick() }}>APP按钮</button>
            </div>
        )
    }
    btnClick() {
        // 1.如果获取的是原生的元素, 那么拿到的就是元素本身
        // 2.如果获取的是类组件元素, 那么拿到的就是类组件的实例对象
        // 3.如果获取的是函数组件元素, 那么什么都拿不到
        console.log(this.myRef.current);
    }
}

export default App;

(2).如何获取函数组件中的元素

  • 如果要获取的不是函数式组件本身,而是想获取函数式组件中的某个元素
  • 那么我们可以使用 Ref转发 来获取 ( React.forwardRef() 
import React from 'react';

// 函数组件
// function About() {
//     return (
//         <div>About</div>
//     )
// }

// Ref转发
const About = React.forwardRef(function(props, myRef){
    return(
        <div>
            <p>我是段落</p>
            <span ref={myRef}>我是Span</span>
        </div>
    )
});

class App extends React.PureComponent {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    render() {
        return (
            <div>
                <About ref={this.myRef} />
                <button onClick={() => { this.btnClick() }}>APP按钮</button>
            </div>
        )
    }
    btnClick() {
        console.log(this.myRef.current);
    }
}

export default App;

8.受控组件

  • 1.什么是受控组件
  • 2.如何定义受控组件
    • value:设置默认初始值
    • onChange:获取用户输入
class App extends React.PureComponent{
    constructor(props){
        super(props);
        this.state = {
            name: 'lnj'
        }
    }
    render(){
        return (
            <form>
                <input type="text"
                       value={this.state.name}
                        onChange={(e)=>{this.change(e)}}/>
            </form>
        )
    }
    change(e){
        console.log(e.target.value);
        this.setState({
            name: e.target.value
        })
    }
}
  • 3.优化受控组件
    • 添加 name 属性
    • 使用 e.target.name 获取 state 的 key(name email phone)
class App extends React.PureComponent{
    constructor(props){
        super(props);
        this.state = {
            name: 'lnj',
            email: '97606813@qq.com',
            phone: '13554499311'
        }
    }
    render(){
        return (
            <form>
                <input type="text"
                       value={this.state.name}
                       name={'name'}
                       onChange={(e)=>{this.change(e)}}/>
                <input type="text"
                       name={'email'}
                       value={this.state.email}
                       onChange={(e)=>{this.change(e)}}/>
                <input type="text"
                       name={'phone'}
                       value={this.state.phone}
                       onChange={(e)=>{this.change(e)}}/>
            </form>
        )
    }
    /*
    nameChange(e){
        console.log(e.target.name);
        this.setState({
            name: e.target.value
        })
    }
    emailChange(e){
        console.log(e.target.name);
        this.setState({
            email: e.target.value
        })
    }
    phoneChange(e){
        console.log(e.target.name);
        this.setState({
            phone: e.target.value
        })
    }
     */
    change(e){
        console.log(e.target.name);
        this.setState({
            [e.target.name]: e.target.value
        })
    }
}
  • 非受控组件:默认状态下的表单元素都是非受控组件

9.高阶组件

  • 什么是高阶组件
    • Higher-Order Components,简称为 HOC
    • 参数组件返回值为新组件 的函数
class Home extends React.PureComponent{
    render(){
        return (
            <div>Home</div>
        )
    }
}
// 定义高阶组件
function enhanceComponent(WrappedComponent) {
    // 定义组件
    class AdvComponent extends React.PureComponent{
        render() {
            return (
                <div>
                    // 调用传递过来的参数(组件)
                    <WrappedComponent/>
                </div>
            )
        }
    }
    // 返回组件
    return AdvComponent;
}
// 调用高阶组件
const AdvComponent = enhanceComponent(Home);

class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div>
                // 使用高阶组件
                <AdvComponent/>
            </div>
        )
    }
}

  • 高阶组件的应用场景
    • 代码复用 / 增强Props / 抽离State / 生命周期拦截

    • 权限控制

抽离State :( 跨组件通讯 context )

const UserContext = React.createContext({});
const {Provider, Consumer} = UserContext;
/*
class Father1 extends React.PureComponent{
    render() {
        return (
            <div>
                <Son1/>
            </div>
        )
    }
}
class Father2 extends React.PureComponent{
    render() {
        return (
            <div>
                <Son2/>
            </div>
        )
    }
}
class Son1 extends React.PureComponent{
    render() {
        return (
            // Consumer:消费数据
            <Consumer>{
                value =>{
                    return (
                        <div>
                            <p>{value.name}</p>
                            <p>{value.age}</p>
                        </div>
                    )
                }
            }</Consumer>
        )
    }
}
class Son2 extends React.PureComponent{
    render() {
        return (
            // Consumer:消费数据
            <Consumer>{
                value =>{
                    return (
                        <ul>
                            <li>{value.name}</li>
                            <li>{value.age}</li>
                        </ul>
                    )
                }
            }</Consumer>
        )
    }
}
 */

// 优化:
class Son1 extends React.PureComponent{
    render() {
        return (
            <div>
                <p>{this.props.name}</p>
                <p>{this.props.age}</p>
            </div>
        )
    }
}
class Son2 extends React.PureComponent{
    render() {
        return (
            <ul>
                <li>{this.props.name}</li>
                <li>{this.props.age}</li>
            </ul>
        )
    }
}
// 定义高阶函数
function EnhancedComponent (WrappedComponent) {
    class Father extends React.PureComponent{
        render() {
            return (
                <Consumer>{
                    value =>{
                        return (
                            /*
                            <WrappedComponent name={value.name} age={value.age}/>
                            */
                            <WrappedComponent {...value} {...this.props}/>
                        )
                    }
                }</Consumer>
            )
        }
    }
    return Father;
}
const Father1 = EnhancedComponent(Son1);
const Father2 = EnhancedComponent(Son2);

class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            // Provider:生产数据
            <Provider value={{name:'zs', age:18}}>
                <Father1/>
                <Father2/>
            </Provider>
        )
    }
}

生命周期拦截:( 跨组件通讯 events)

import React from 'react';
import {EventEmitter} from 'events';

const UserContext = React.createContext({});
const {Provider, Consumer} = UserContext;
const eventBus = new EventEmitter();
class Son1 extends React.PureComponent{
    /*
    constructor(props){
        super(props);
        this.state = {
            list: []
        }
    }
    componentDidMount() {
        eventBus.addListener('update', this.update.bind(this));
    }
    componentWillUnmount() {
        eventBus.removeListener('update', this.update.bind(this));
    }
    update(list){
        this.setState({
            list: list
        })
    }
     */

    render() {
        return (
            <div>
                <p>{this.props.name}</p>
                <p>{this.props.age}</p>
                <p>{this.props.country}</p>
                {
                    this.props.list.map(name=>{
                        return <p key={name}>{name}</p>
                    })
                }
            </div>
        )
    }
}
class Son2 extends React.PureComponent{
    /*
    constructor(props){
        super(props);
        this.state = {
            list: []
        }
    }
    componentDidMount() {
        eventBus.addListener('update', this.update.bind(this));
    }
    componentWillUnmount() {
        eventBus.removeListener('update', this.update.bind(this));
    }
    update(list){
        this.setState({
            list: list
        })
    }
     */
    render() {
        return (
            <ul>
                <li>{this.props.name}</li>
                <li>{this.props.age}</li>
                <li>{this.props.country}</li>
                {
                    this.props.list.map(name=>{
                        return <li key={name}>{name}</li>
                    })
                }
            </ul>
        )
    }
}
// 定义高阶函数
function EnhancedComponent (WrappedComponent) {
    class Father extends React.PureComponent{
        constructor(props){
            super(props);
            this.state = {
                list: []
            }
        }
        componentDidMount() {
            eventBus.addListener('update', this.update.bind(this));
        }
        componentWillUnmount() {
            eventBus.removeListener('update', this.update.bind(this));
        }
        update(list){
            this.setState({
                list: list
            })
        }
        render() {
            return (
                <Consumer>{
                    value =>{
                        return (
                            <WrappedComponent {...value} {...this.props} {...this.state}/>
                        )
                    }
                }</Consumer>
            )
        }
    }
    return Father;
}
const Father1 = EnhancedComponent(Son1);
const Father2 = EnhancedComponent(Son2);

class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <Provider value={{name:'zs', age:18}}>
                <Father1 country={'中国'}/>
                <Father2 country={'俄罗斯'}/>
                <button onClick={()=>{this.btnClick()}}>按钮</button>
            </Provider>
        )
    }
    btnClick(){
        eventBus.emit('update', ['鲁班', '虞姬', '黄忠']);
    }
}

export default App;

权限控制:

class Info extends React.PureComponent{
    render() {
        return (
            <div>用户信息</div>
        )
    }
}
class Login extends React.PureComponent{
    render() {
        return (
            <div>用户登录</div>
        )
    }
}
function EnhancedComponent (WrappedComponent) {
    class Authority extends React.PureComponent{
        render() {
            if(this.props.isLogin){
                return <Info/>
            }else{
                return <Login/>
            }
        }
    }
    return Authority;
}
const AuthorityInfo = EnhancedComponent(Info);

class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <AuthorityInfo isLogin={true}/>
        )
    }
}

10.Portal

  • 默认情况下所以的组件都是渲染到 root 元素中的
  • Portal 提供了一种将组件渲染到其它元素中的能力
import React from 'react';
import ReactDOM from 'react-dom';

class Modal extends React.PureComponent{
    render() {
        /*
        this.props.children: 可以获取到当前组件所有的子元素或者子组件
        createPortal: 接收两个参数
        第一个参数: 需要渲染的内容
        第二个参数: 渲染到什么地方
        * */
        return ReactDOM.createPortal(this.props.children, document.getElementById('other'));
    }
}
class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            <div id={'app'}>
                <Modal>
                    {/* 定义子元素 */}
                    <div id={'modal'}>Modal</div>
                </Modal>
            </div>
        )
    }
}

export default App;

11.Fragment

  • 由于React规定, 组件中只能有一个根元素
  • 所以每次编写组件的时候, 我们都需要在最外层包裹一个冗余的标签
  • 如果不想渲染这个冗余的标签, 那么就可以使用 Fragment 来代替

代码冗余:

如何改进:

class Home extends React.PureComponent{
    constructor(props){
        super(props);
        this.state = {
            heroList: ['鲁班', '虞姬', '黄忠']
        }
    }
    render() {
        // 如果组件的结构比较复杂, 那么只能有一个根元素
        return (
            /*
            <React.Fragment>
                <p>{this.state.heroList[0]}</p>
                <p>{this.state.heroList[1]}</p>
                <p>{this.state.heroList[2]}</p>
            </React.Fragment>
            */
            // 如果需要给Fragment添加key, 那么就不能使用语法糖
            <>
                {
                    this.state.heroList.map(name=>{
                        return (
                            <React.Fragment key={name}>
                                {/* <p key={name}>{name}</p> */}
                                <p>{name}</p>
                            </React.Fragment>
                        )
                    })
                }
            </>
        )
    }
}
class App extends React.PureComponent{
    constructor(props){
        super(props);
    }
    render(){
        return (
            /*
            <React.Fragment>
                <Home/>
            </React.Fragment>
            */
            // 下面的这种写法就是上面写法的语法糖
            <>
                <Home/>
            </>
        )
    }
}

12.StrictMode

  • 1.什么是 StrictMode
    • 作用
      • 开启严格模式, 检查后代组件中是否存在潜在问题
    • 注意点
      • 和 Fragment 一样, 不会渲染出任何UI元素
      • 仅在'开发模式'下有效
  • 2.StrictMode 检查什么
    • 检查过时或废弃的属性/方法/...
    • 检查意外的副作用
    • 这个组件的 constructor 会被调用两次
    • 检查这里写的一些逻辑代码被调用多次时,是否会产生一些副作用
ReactDOM.render(
    <React.StrictMode>
        <App/>
    </React.StrictMode>
    , document.getElementById('root'));

五、React样式

  • React中的样式
    • React并没有像Vue那样提供特定的区域给我们编写CSS代码
    • 所以你会发现在React代码中, CSS样式的写法千奇百怪

1.内联样式

  • 内联样式的优点
    • 内联样式, 样式之间不会有冲突
    • 可以动态获取当前state中的状态
  • 内联样式的缺点
    • 写法上都需要使用驼峰标识
    • 某些样式没有提示
    • 大量的样式, 代码混乱
    • 某些样式无法编写(比如伪类/伪元素)
import React from 'react';

class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            color: 'red'
        }
    }
    render(){
        return(
            <div>
                {/* 1.内联样式 */}
                <p style={{fontSize: '20px', color: this.state.color}}>我是段落1</p>
                <p style={{fontsize: '20px', color: 'green'}}>我是段落2</p>
                <button onClick={()=>{this.btnClick()}}>按钮</button>
            </div>
        )
    }
    btnClick(){
        this.setState({
            color: 'blue'
        })
    }
}

export default App;

2.外链样式

  • 将CSS代码写到一个单独的CSS文件中, 在使用的时候导入进来
  • 外链样式的优点
    • 编写简单, 有代码提示, 支持所有CSS语法
  • 外链样式的缺点
    • 不可以动态获取当前state中的状态
    • 属于全局的css,样式之间会相互影响(可以设置id避免相互影响)

components/Home.js

import React from 'react';
// 2.外链式
// 导入样式
import './Home.css';

class Home extends React.Component{
    render(){
        return(
            <div id='home'>
                <p>我是home段落</p>
                <a href="www.it66.com">我是home超链接</a>
            </div>
        )
    }
}
export default Home;

components/Home.css

3.Css Module

  • React的脚手架,已经内置了css modules的配置
  • .css/.less/.scss 等样式文件都修改成 .module.css/.module.less/.module.scss 等;
  • Css Modules优点
    • 编写简单, 有代码提示, 支持所有CSS语法
    • 解决了全局样式相互污染问题
  • Css Modules缺点
    • 不可以动态获取当前 state 中的状态

components/About.js

import React from 'react';
// 导入样式
import AboutStyle from './About.module.css';

class About extends React.Component{
    render(){
        return(
            <div id='home'>
                {/* 3.Css Module */}
                <p className={AboutStyle.title}>我是about段落</p>
                <a href="www.it66.com" className={AboutStyle.link}>我是about超链接</a>
            </div>
        )
    }
}
export default About;

components/About.moudule.css

  • 模板字符串
    • 在JS中除了可以通过()来调用函数以外,其实我们还可以通过模板字符串来调用函数
    • 通过模板字符串调用函数规律
      • 参数列表中的第一个参数是一个数组, 这个数组中保存了所有不是插入的值
      • 参数列表的第二个参数开始, 保存的就是所有插入的值
const name = 'lnj';
const age = 18;
/*
const str = `my name is ${name}, my age is ${age}`;
console.log(str); // my name is lnj, my age is 18
*/

function test(...args) {
    console.log(args);
}
// 在JS中除了可以通过()来调用函数以外,
// 其实我们还可以通过模板字符串来调用函数
// test(1, 3, 5); // [ 1, 3, 5 ]
// test`1, 3, 5`; // [ [ '1, 3, 5' ] ]
test`1, 3, 5, ${name}, ${age}`; // [ [ '1, 3, 5, ', ', ', '' ], 'lnj', 18 ]
  • 总结
    • 我们可以拿到模板字符串中所有的内容
    • 我们可以拿到模板字符串中所有非插入的内容
    • 我们可以拿到模板字符串中所有插入的内容
    • 所以我们就可以对模板字符串中所有的内容进行单独的处理

4.CSS-in-JS

  • 1.CSS-in-JS
    • React认为结构和逻辑是密不可分的,所以在React中结构代码也是通过JS来编写的
    • 正是受到React这种思想的影响, 所以就有很多人开发了用JS来编写CSS的库
      • styled-components / emotion
    • 利用JS来编写CSS, 可以让CSS具备样式嵌套,函数定义,逻辑复用,动态修改状态等特性
      • 也就是说, 从某种层面上, 提供了比过去Less/Scss更为强大的功能
      • 所以Css-in-JS, 在React中也是一种比较推荐的方式
  • 2.styled-components 使用
    • 1.安装 styled-components
      • npm install styled-components --save
    • 2.导入 styled-components
      •  import styled from 'styled-components';
    • 3.利用 styled-components 创建组件并设置样式
      • 注意:vscode 需要安装 vscode-styled-components 插件,才能有代码提示    
import React from 'react';
import styled from 'styled-components';

// 4.CSS-in-JS
const StyleDiv = styled.div`
    p{
        font-size: 20px;
        color: red;
    }
    a{
        font-size: 25px;
        color: green;
    }
`
class Top extends React.Component{
    render(){
        return(
            <StyleDiv>
                <p>我是top段落</p>
                <a href={'www.it666.com'}>我是top超链接</a>
            </StyleDiv>
        )
    }
}
export default Top;
  • 3.styled-components 特性
    • props
    • attrs

props特性:

attrs特性:

import React from 'react';
import styled from 'styled-components';

const StyleDiv = styled.div`
    p{
        font-size: 20px;
        /* 1.props特性 */
        color: ${props => props.color};
    }
    a{
        font-size: 25px;
        color: green;
    }
`
// 2.attrs特性
// 注意点:调用完attrs方法之后, 这个方法返回的还是一个函数
// 所以我们还可以继续通过字符串模板来调用
const StyleInput = styled.input.attrs({
    // 密文input输入框
    type: 'password'
})``

class Bottom extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            color: 'pink'
        }
    }
    render(){
        return(
            // 组件传参,参数会保存到props中
            <StyleDiv color={this.state.color}>
                <p>我是bottom段落</p>
                <a href={'www.it666.com'}>我是bottom超链接</a>
                <button onClick={()=>{this.btnClick()}}>按钮</button>
                
                {/* <StyleInput type={'password'}></StyleInput> */}
                <StyleInput></StyleInput>
            </StyleDiv>
        )
    }
    btnClick(){
        this.setState({
            color: 'blue'
        })
    }
}
export default Bottom;
  • 4.如何通过 styled-components 设置主题(通用样式设置,比如:字体、大小、颜色等)
    • ThemeProvider

App.js

components/About.js

components/Home.js

  • 5.styled-components 继承

import React from 'react';
import './App.css'
import styled from 'styled-components'
/*
const StyleDiv1 = styled.div`
  font-size: 100px;
  color: red;
  background: blue;
`;
const StyleDiv2 = styled.div`
  font-size: 100px;
  color: green;
  background: blue;
`;
*/
const BaseDiv = styled.div`
  font-size: 100px;
  background: blue;
`;
// styled-components 继承
const StyleDiv1 = styled(BaseDiv)`
  color: red;
`;
const StyleDiv2 = styled(BaseDiv)`
  color: green;
`;
class App extends React.Component{
    render(){
        return (
            <div>
                <StyleDiv1>我是div1</StyleDiv1>
                <StyleDiv2>我是div2</StyleDiv2>
            </div>
        );
    }
}
export default App;

六、React 动画

  • 1.React 过渡动画
    • 在React中我们可以通过原生的CSS来实现过渡动画,
    • 但是React社区为我们提供了 react-transition-group 帮助我们快速过渡动画
  • 2.动画组件
    • Transition
      • 该组件是一个和平台无关的组件(不一定要结合CSS);
      • 在前端开发中,我们一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
    • CSSTransition
      • 在前端开发中,通常使用CSSTransition来完成过渡动画效果
    • SwitchTransition
      • 两个组件显示和隐藏切换时,使用该组件
    • TransitionGroup
      • 将多个动画组件包裹在其中,一般用于列表中元素的动画;

原生动画

const StyleDiv  = styled.div`
  width: ${props => props.width};
  height: ${props => props.height};
  background: skyblue;
  opacity: ${props => props.opacity};
  transition: all 3s;
`
class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            width: 0,
            height: 0,
            opacity:0
        }
    }
    render(){
        return (
            <div>
                <StyleDiv {...this.state}></StyleDiv>
                <button onClick={()=>{this.btnClick()}}>按钮</button>
            </div>
        );
    }
    btnClick(){
        this.setState({
            width: '100px',
            height: '100px',
            opacity:1
        })
    }
}
  • CSSTransition状态
    • CSSTransition有三个状态
      • appear: 初始
      • enter : 进入
      • exit: 退出
    • 当组件第一次加载时候会自动查找
      • appear / -appear-active / -appear-done
    • 当组件显示时会自动查找
      • enter / -enter-active / -enter-done
    • 当组件退出时会自动查找
      • exit / -exit-active / -exit-done
  • 如何通过 CSSTransition 来实现过渡效果?
    • 1.1 安装react-transition-group
      • npm install react-transition-group --save
    • 1.2 从安装好的库中导入CSSTransition
      • import {CSSTransition} from 'react-transition-group';
    • 1.3 利用CSSTransition将需要执行过渡效果的组件或元素包裹起来
    • 1.4 编写对应的CSS动画
      • 实现: .-enter / .-enter-active / .-enter-done
    • 1.5 给CSSTransition添加一些属性
    • in属性 
      • 取值是一个布尔值, 如果取值为false表示触发退出动画, 如果取值是true表示       触发进入动画
    • classNames属性
      • 指定动画类名的前缀
    • timeout属性
      • 设置动画超时时间

App.js

import React from 'react';
import './App.css'
import {CSSTransition} from 'react-transition-group';

class App extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            isShow: false
        }
    }
    render(){
        return (
            <div>
                <CSSTransition in={this.state.isShow}
                                classNames={'box'}
                                timeout={3000}>
                    <div></div>
                </CSSTransition>
                <button onClick={()=>{this.btnClick()}}>显示</button>
            </div>
        );
    }
    btnClick(){
        this.setState({
            isShow: true
        })
    }
}
export default App;

App.css

.box-enter{
    /*进入动画执行之前绑定的类名*/
    width: 0;
    height: 0;
    opacity: 0;
    background: skyblue;
}
.box-enter-active{
    /*进入动画执行过程中绑定的类名*/
    width: 100px;
    height: 100px;
    opacity: 1;
    transition: all 3s;
}
.box-enter-done{
    /*进入动画执行完毕之后绑定的类名*/
    width: 100px;
    height: 100px;
    opacity: 1;
    background: red;
}

七、React 路由

1.什么是路由

  • 路由维护了URL地址组件映射关系, 通过这个映射关系,
  • 我们就可以根据不同的URL地址,去渲染不同的组件

2.如何使用路由

  • 2.1.安装 react-router-dom
    • npm install react-router-dom
  • 2.2.通过指定监听模式
    • BrowserRouter:  history模式     http://www.it666.com/home
    • HashRouter:       hash模式        http://www.it666.com/#/home
  • 2.3.通过 Link  修改路由URL地址
  • 2.4.通过 Route  匹配路由地址
import {BrowserRouter, HashRouter, Link, Route} from 'react-router-dom';

class App extends React.PureComponent{

    render(){
        // 需求: 界面上有两个按钮, 点击不同按钮显示不同组件
        return (
            <div>
                {/*
                1.指定路由的监听模式
                */}
                <HashRouter>
                    {/*
                    2.修改URL的资源地址
                    */}
                    <Link to={'/home'}>Home</Link>
                    <Link to={'/about'}>About</Link>
                    {/*
                    3.维护URL和组件的关系
                    */}
                    <Route path={'/home'} component={Home}/>
                    <Route path={'/about'} component={About}/>
                </HashRouter>
            </div>
        )
    }
}

3.React 路由注意点

  • react-router4之前, 所有路由代码都是统一放到react-router中管理的
  • react-router4开始, 拆分为了两个包react-router-dom和react-router-native
  • react-router-dom 在浏览器中使用路由;react-router-native 在原生应用中使用路由
  • BrowserRouter history模式使用的是H5的特性, 所以兼容性会比HashRouter hash模式差一些
  • 在企业开发中如果不需要兼容低级版本浏览器, 建议使用 BrowserRouter
  • 如果需要兼容低级版本浏览器, 那么只能使用 HashRouter
  • 无论是Link还是Route,都只能放到BrowserRouter和HashRouter中才有效
  • 3.1 Route 注意点
    • 默认情况下Route在匹配资源地址时, 是模糊匹配
    • 如果必须和资源地址一模一样才匹配, 那么需要添加 exact 属性, 开启精准匹配
  • <Route exact path={'/home'} component={Home}/>
    <Route exact path={'/home/about'} component={About}/>
  • 3.2 Link注意点
    • 默认情况下Link会渲染成一个 a标签, 如果想渲染成其他的元素, 可以通过手动路由     跳转来实现
  • 3.3 NavLink注意点
    • 默认情况下NavLink在匹配资源地址时, 是模糊匹配
    • 如果必须和资源地址一模一样才匹配, 那么需要添加 exact 属性, 开启精准匹配
    • NavLink有一个 activeStyle 属性,用于指定样式
  • <NavLink exact to={'/home'} activeStyle={{color:'red'}}>Home</NavLink>
    <NavLink exact to={'/home/about'} activeStyle={{color:'red'}}>About</NavLink>

4. Switch 仅匹配一个

  • 默认情况下,路由会从上至下匹配所有的Route, 只要匹配都会显示
  • 但是在企业开发中,大部分情况下, 我们希望的是一旦有一个匹配到了后续就不要再匹配
  • 此时我们就可以通过 Switch 来实现
  • <Switch>
        <Route exact path={'/home'} component={Home}/>
        <Route exact path={'/about'} component={About}/>
        {/* 如果Route没有指定path, 那么表示这个Route和所有的资源地址都匹配 */}
        <Route component={Other}/>
    </Switch>

5. Redirect 重定向

  • 资源重定向, 也就是可以在访问某个资源地址的时候,重定向到另外一个资源地址
  • 例如: 访问 /user 用户界面, 如果未登录,则重定向到 /login 登录界面

6.嵌套路由

  • 路由里面又有路由, 我们就称之为嵌套路由
  • 如果要使用嵌套路由, 那么外层路由不能添加精准匹配 exact

  • 子路由不用使用 BrowserRouter 或 HashRouter 来包裹 NavLink/Switch/Route

App.js

import React from 'react';
import Discover from './components/Discover'
import { BrowserRouter, NavLink, Route, Switch } from 'react-router-dom';
/*
1.嵌套路由(子路由):

  路由里面又有路由, 我们就称之为嵌套路由
*/

class App extends React.PureComponent {
    render() {
        return (
            <div>
                <BrowserRouter>
                    <NavLink to={'/discover'} activeStyle={{ color: 'red' }}>广场</NavLink>

                    <Switch>
                        {/* 注意:如果要使用嵌套路由, 那么外层路由不能添加精准匹配 exact */}
                        <Route path={'/discover'} component={Discover} />
                    </Switch>
                </BrowserRouter>
            </div>
        )
    }
}

export default App;

components/Discover.js

import React from 'react';
import { NavLink, Switch, Route } from "react-router-dom";


// 定义函数组件
function Hot() {
    return (
        <div>推荐</div>
    )
}
function TopList() {
    return (
        <div>排行榜</div>
    )
}
function PlayList() {
    return (
        <div>歌单</div>
    )
}

// 定义类组件
class Discover extends React.PureComponent{
    render(){
        return(

            // 注意点: 由于当前组件是在 BrowserRouter 或 HashRouter 中显示的
            //        所以在当前组件中不用使用 BrowserRouter 或 HashRouter 来包裹 NavLink/Switch/Route
            <div>
                {/* 之所以指定为/discover,是将其设置为二级路由的默认显示界面 */}
                <NavLink exact to={'/discover'} activeStyle={{ color: 'blue' }}>推荐</NavLink>

                <NavLink exact to={'/discover/toplist'} activeStyle={{ color: 'blue' }}>排行榜</NavLink>
                <NavLink exact to={'/discover/playlist'} activeStyle={{ color: 'blue' }}>歌单</NavLink>
                <Switch>
                    <Route exact path={'/discover'} component={Hot} />
                    <Route exact path={'/discover/toplist'} component={TopList} />
                    <Route exact path={'/discover/playlist'} component={PlayList} />
                </Switch>
            </div>
        )
    }
}

export default Discover;

7.手动路由跳转

  • 不通过Link/NavLink来设置资源地址, 而是通过JS来设置资源地址
    • 手动指定路由
      •  { this.btnClick() }}>歌单
      • Hash模式:window.location.hash = 'URL地址';

      • 子路由:this.props.history.push('URL地址');(组件是由路由创建出来的)

class Discover extends React.PureComponent{
    render(){
        return(

            // 注意点: 由于当前组件是在 BrowserRouter 或 HashRouter 中显示的
            //        所以在当前组件中不用使用 BrowserRouter 或 HashRouter 来包裹 NavLink/Switch/Route
            <div>
                {/* 之所以指定为/discover,是将其设置为二级路由的默认显示界面 */}
                <NavLink exact to={'/discover'} activeStyle={{ color: 'blue' }}>推荐</NavLink>

                <NavLink exact to={'/discover/toplist'} activeStyle={{ color: 'blue' }}>排行榜</NavLink>
                <NavLink exact to={'/discover/playlist'} activeStyle={{ color: 'blue' }}>歌单</NavLink>

                {/* 手动路由跳转 */}
                <button onClick={() => { this.btnClick() }}>歌单</button>

                <Switch>
                    <Route exact path={'/discover'} component={Hot} />
                    <Route exact path={'/discover/toplist'} component={TopList} />
                    <Route exact path={'/discover/playlist'} component={PlayList} />
                </Switch>
            </div>
        )
    }
    btnClick(){
        // 1.如果是Hash模式, 那么只需要通过JS设置Hash值即可
        // window.location.hash = '/discover/playlist';

        // 如果一个组件是通过路由创建出来的, 那么系统就会自动传递一个history给我们
        // 我们只需要拿到这个history对象, 调用这个对象的push方法, 通过push方法修改资源地址即可
        // console.log(this.props.history);

        // 2.通过history对象的push方法
        this.props.history.push('/discover/playlist');
    }
}
  • 注意点:
    • 只有通过路由创建出来的组件才有history对象, 所以不能在根组件中使用手动路由跳转
    • 如果想在根组件中使用手动路由跳转, 那么需要借助一个withRouter高阶组件
import React from 'react';
import Discover from './components/Discover'
import { BrowserRouter, HashRouter, NavLink, Route, Switch, withRouter} from 'react-router-dom';

class App extends React.PureComponent {
    render() {
        return (
            <div>
                {/* <HashRouter> */}
                    <NavLink to={'/discover'} activeStyle={{ color: 'red' }}>广场</NavLink>
                    {/* 手动路由跳转 */}
                    <button onClick={() => { this.btnClick() }}>广场</button>

                    <Switch>
                        <Route path={'/discover'} component={Discover} />
                    </Switch>
                {/* </HashRouter> */}
            </div>
        )
    }
    btnClick() {
        // 1.Hash模式, 通过设置Hash值即可
        window.location.hash = '/discover';
        /*
        如果一个组件是通过路由创建的, 那么系统就会自动给这个组件传递一个history对象
        但是如果一个组件不是通过路由创建的, 那么系统就不会给这个组件传递一个history对象

        如果现在非路由创建出来的组件中使用history对象, 那么可以借助withRouter高阶组件
        只要把一个组件传递给withRouter方法, 那么这个方法就会通过路由将传入的组件创建出来

        注意点: 如果一个组件要使用路由创建, 那么这个组件必须包裹在 BrowserRouter, HashRouter中
        * */
       // 2.通过history对象的push方法(还需借助withRouter高阶组件)
        this.props.history.push('/discover');
    }
}

export default withRouter(App);

8.路由传参

  • 1.URL参数
    • ?key=value&key=value
  • 2.路由参数(动态路由)
    • /path/:key
  • 3.对象
    • state 即为要传递的数据
{
    pathname: "/user",
    search: "",
    hash: "",
    state: obj
}

App.js

class App extends React.PureComponent {
    render() {
        let obj = {
            name: 'lnj',
            age: 18,
            gender: 'man'
        };
        return (
            <div>
                <HashRouter>
                    {/* 1.URL参数 */}
                    <NavLink to={'/home?name=lnj&age=18'} activeStyle={{ color: 'red' }}>Home</NavLink>
                    <NavLink to={'/about/lnj/age'} activeStyle={{ color: 'red' }}>About</NavLink>
                    {/* 3.对象 */}
                    <NavLink to={{
                        pathname: "/user",
                        search: "",
                        hash: "",
                        state: obj
                    }} activeStyle={{ color: 'red' }}>User</NavLink>
                    <Switch>
                        <Route path={'/home'} component={Home} />
                        {/* 2.路由参数(动态路由) */}
                        <Route path={'/about/:name/:age'} component={About} />
                        <Route path={'/user'} component={User} />
                    </Switch>
                </HashRouter>
            </div>
        )
    }
}

components/Home.js(URL参数)

class About extends React.PureComponent{
    constructor(props){
        super(props);
        // console.log(this.props.location);
        // console.log(this.props.location.search);
        // substring:从第一个字符开始截取
        let query = this.props.location.search.substring(1);
        query = query.split('&');
        // ["name=lnj", "age=18"]
        // console.log(query);
        let obj = {};
        query.forEach((item) => {
            // name=lnj  age=18
            let temp = item.split('=');
            // ["name", "lnj"]  ["age", 18]
            // console.log(temp);
            obj[temp[0]] = temp[1];
        });
        console.log(obj);
    }
    render(){
        return (
            <div>Home</div>
        )
    }
}
  • this.props.match.params   (路由参数)

  • this.props.location.state  (对象)

9.路由集中管理

  • 现在虽然我们能通过路由实现组件切换, 但是现在我们的路由都比较分散,
  • 不利于我们管理和维护
  • 所以React也考虑到了这个问题, 也给我们提供了统一管理路由的方案
  • 1.安装 react-router-config
    • npm install react-router-config
  • 2.在src目录下,新建router文件
  • 3.编写 index.js文件
  • 4.使用 routers
    • {renderRoutes(routers)}         (一级路由)

src/router/index.js:

import Home from '../components/Home'
import About from '../components/About'
import User from '../components/User'

const routers=[
    {
        // 访问/home地址时,调用Home组件 
        path: '/home',         // 路由地址
        exact: true,           // 开启精准匹配
        component: Home        // 匹配组件
    },
    {
        path: '/about/:name/:age',
        exact: true,
        component: About
    },
    {
        path: '/user',
        exact: true,
        component: User
    },
]

export default routers;

  • 二级路由如何定义
    • routes: [ ]

  • {renderRoutes(this.props.route.routes)}      (二级路由)

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值