react学习

一、简介和简单demo

1 react特点

采用组件化模式,声明式编码,提高开发效率和组件复用率

虚拟DOM和优秀的Diffing算法,尽量减少与真实DOM的交互。这是react高效的原因

2 简单小Demo

首先下载以下三个js,这三个js是react基本的js库

react.development.js//react核心库
react-dom.development.js,//react操作DOM工具
babel.js,//用于将jsx转换为js
创建存放代码的目录,比如E:\code\react_basic,创建js文件夹存放这三个js
创建01_hello_react目录存放我们的html

 

 hello_react.html

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        //1.创建虚拟DOM
        const VDOM = <h1>Hello,React</h1>
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'))
    </script>
</body>
</html>

右键选择open with live server(如果没有自行百度)在浏览器中打开

 

 

 

 注意:有两个warning,第一个提示我们安装开发者工具,等我们装上开发者工具就没有了;第二个提示我们使用了内嵌的babel翻译,建议我们进行预编译以便提高性能

3 为什么用jsx?

我们再创建一个html,看一下不用jsx行不行?

创建02hello_react_no_jsx.html

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript"> /*此处一定要写babel*/
        //1.创建虚拟DOM
        const VDOM = React.createElement('h1', {id:'title'}, 'Hello React')//我们没有引入babel.js,使用React创建一个element
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM, document.getElementById('test'))
    </script>
</body>
</html>

发现,不用jsx也是可以的。

但是,如果要求

const VDOM = React.createElement('h1', {id:'title'}, 'Hello React')

有好几层,我们创建是不是就需要多次调用React.createElement来满足我们的要求,这样是比较麻烦的。

比如标注1:React.createElement('h1', {id:'title'},React.createElement('span', {}, 'Hello React'))

所以我们使用jsx语法就方便多了。

标注2:const VDOM = <h1><span>Hello,React</span></h1>

其实,经过babel翻译之后,上面的标注2语法会被翻译成标注1.

二、jsx规范以及虚拟DOM和真实DOM比较

1 虚拟DOM和真实DOM比较

1 虚拟DOM本质是Object对象(一般js对象)

2 虚拟DOM比较“轻”,真实DOM比较“重”。因为虚拟DOM是在React内部使用,无需真实DOM那么多属性。

3 虚拟DOM最终会被React转化为真实DOM,呈现在页面上

2 jsx规则

1 定义虚拟DOM时,不要写引号

const VDOM = <h1>Hello,React</h1>

2 标签中混入js表达式时,要用花括号{}

const myId='aaaa';
//1.创建虚拟DOM
const VDOM = <h1 id={myId}>Hello,React</h1>

3 样式的类名指定不要用class,要用className

4 内联样式要用style={{key:value}}的形式去写,外层花括号表示要写js表达式(要区分表达式和语句),内层花括号表示要写一个对象。

5 虚拟DOM必须只有一个根标签

6 标签必须闭合

7 标签首字母,

若小写字母开头,则将改标签转为html同名元素,若html中没有该标签对应的同名元素,则报错。

若大写字母开头,react就去渲染对应的组件,若没有该组件定义则报错。

三、react组件编程

1 模块化和组件化

模块化,指的是js代码,我们按照一个个业务模块来写js代码,一个模块对应一个js文件,只包含了对js的规整

组件化,指的是一个组件内包含了html,css,js,音视频等媒体。

2 函数式组件

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        //1.创建函数式组件
        function Demo()
        {
            return <h2>我是用函数定义的组件</h2>
        }
        //2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
    </script>
</body>
</html>

上面代码的执行过程

1 React解析组件标签,找到Demo组件

2 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面上。

3 类式组件

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        //1.创建类式组件
        class Demo extends React.Component {
            render() {
                return <h2>我是用类定义的组件</h2>
            }
        }
        //2.渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
    </script>
</body>
</html>

上面代码的执行过程

1 React解析组件标签,找到Demo组件

2 发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法

3 将返回的虚拟DOM转为真实DOM,随后呈现在页面上。

四、组件实例的三大核心属性-state

0 复习知识

0.1  实例中的this对象

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <script type="text/javascript">
        function demo() {
            'use strict'
            console.log(this);
        }
        demo();
    </script>
</body>
</html>

不用use strict输出

Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

使用use strict输出

undefined

再举个例子

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <script type="text/javascript">
        class Weather {
            changeWeather(){
                console.log(this);
            }
        }
        const weather = new Weather();
        weather.changeWeather();
        const weather2 = weather.changeWeather;
        weather2();
    </script>
</body>
</html>

输出:

Weather {}
undefined

这里 weather.changeWeather(); 是对象调用, weather2(); 是直接调用。

changeWeather方法放到哪里了?-----类的原型对象上,在堆上,供实例使用

类中所有方法都在局部开启了严格模式。

0.2 bind

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <script type="text/javascript">
        function demo() {
            console.log(this);
        }
        demo();
        const x = demo.bind({a:1,b:2})
        x()

    </script>
</body>
</html>

输出:

Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
 {a: 1, b: 2}

demo.bind为我们生成了一个新的函数,这个函数返回值为{a:1,b:2}

1 state

下面实例我们实现一个简单的功能,点击一串文字,来回切换文字内容

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Weather extends React.Component {
            constructor(props){
                super(props)
                this.state = {isHot:false}
            }
            render() {
                console.log(this)
                const {isHot} = this.state
                return <h2 onClick={this.changeWather}>今天天气很{isHot ? '炎热':'凉爽'}</h2>
            }
        //重要:changeWeather放在哪里?-----Weather的原型对象上,供实例使用
            //由于changerWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
             //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
            changeWather(){
                console.log(this);
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test'))
    </script>
</body>
</html>

注意点:

1 不能写成 <h2 onClick={changeWather()}>今天天气很{isHot ? '炎热':'凉爽'}</h2> ,因为{}内需要是表达式,表达式一定会返回一个变量, changeWeather() 返回的是undefined,要写成 changeWeather ,其返回的是一个函数,只有这样点击的时候才能够调用到函数

2 要加上this <h2 onClick={this.changeWather}> ,这是由js类的语法决定的,不能在类中直接调用类的另一个函数,类中的函数只能由类的实例来调用,所以要加上this来调用。

上述代码执行时,changeWeather中打印出来的是undefined,就是因为代码中注释中所描述的原因。解决这个问题,需要使用bind把原型中的changeWeather函数定义放到当前对象中。

代码修改如下

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Weather extends React.Component {
            constructor(props){
                super(props)
                this.state = {isHot:false}
                this.changeWeather = this.changeWeather.bind(this);//右边部分顺着原型链找到原型中的changeWeather定义,然后通过bind生成一个新的函数,该函数返回当前对象this。左边把该新生成的函数赋值给this.changeWeather
            }
            render() {
                console.log(this)
                const {isHot} = this.state
                return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热':'凉爽'}</h2>
            }
            //changeWeather放在哪里?-----Weather的原型对象上,供实例使用
            //由于changerWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
            //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
            changeWeather(){
                console.log(this);
                //获取原来的isHot值
                const isHot = this.state.isHot;
                //状态state不可直接更改,下面这行就是直接更改
                //this.state.isHot=!isHot;
                this.setState({isHot:!isHot});
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'))
    </script>
</body>
</html>

 

 

 至此,我们可以页面查看现象,刚开始显示“炎热”,点击后显示“凉爽”,再点击显示“炎热”,依次循环。

当state变化时,react都不辱使命的重新执行一次render

 

对上面代码的精简和优化

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <!--准备好一个容器-->
    <div id="test"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Weather extends React.Component {
            //初始化状态
            state = {isHot:false}
            render() {
                console.log(this)
                const {isHot} = this.state
                return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热':'凉爽'}</h2>
            }
            //自定义方法----要用赋值语句+箭头函数的方法
            changeWeather=()=>{
                console.log(this);
                const isHot = this.state.isHot;
                this.setState({isHot:!isHot});
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Weather/>, document.getElementById('test'))
    </script>
</body>
</html>

1 去掉了构造函数,state直接在类里面定义

2 去掉了构造函数中changeWeather使用bind方法

3 changeWeather方法改为赋值语句,且使用箭头函数,箭头函数的作用是,当this方法为undefined时,使用其上层对象来作为this。

总结:

state是组件对象最重要的属性,值是对象

组件被称为“状态机”,通过更新组件的state来更新对应页面显示(重新渲染组件)

组件中render方法中的this为组件实例对象,组件自定义方法中this为undefined,如何解决? a.通过函数对象的bind强制绑定this,b.箭头函数

五、组件实例的三大核心属性-props

0 展开运算符复习

View Code

 

1 props的基本使用

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <div id="test2"></div>
    <div id="test3"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <script type="text/babel"> /*此处一定要写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="19" sex=""/>, document.getElementById('test1'))
        ReactDOM.render(<Person name="jerry" age="20" sex=""/>, document.getElementById('test2'))
        // ReactDOM.render(<Person name="lucy" age="21" sex="女"/>, document.getElementById('test3'))

        //另一种写法
        const p = {name:"lucy", age:"21", sex:""}
        ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))//原生js不允许使用展开运算符展开一个对象,但是react+babel就可以了,且展开运算符只能用在属性传值的情形。
    </script>
</body>
</html>

 

下面我们有个需求,姓名必须指定,且为字符串;性别为字符串,如果不指定默认为“男”;年龄为字符串,且数字类型,默认18;年龄显示为实际年龄+1;

我们需要引入PropTypes.js

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <div id="test2"></div>
    <div id="test3"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写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+1}</li>
                    </ul>
                )
            }
        }
        //对标签属性进行类型限制
        Person.propTypes = {
            name: PropTypes.string.isRequired,
            sex: PropTypes.string,
            age: PropTypes.number,
            speak: PropTypes.func,
        }
        Person.defaultProps = {
            sex: '不男不女',
            age: 18,
        }
        //渲染组件到页面
        ReactDOM.render(<Person name="tom" age={19} sex="" speak={speak}/>, document.getElementById('test1'))
        ReactDOM.render(<Person name="jerry" age={20} sex=""/>, document.getElementById('test2'))
        // ReactDOM.render(<Person name="lucy" age="21" sex="女"/>, document.getElementById('test3'))

        //另一种写法
        const p = {name:'lucy', age:21, sex:""}
        ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))//原生js不允许使用展开运算符展开一个对象,但是react+babel就可以了。

        function speak() {
            console.log('我说话了');
        }
    </script>
</body>
</html>

 

2 上述代码的优化

上述代码我们使用了Person.propTypes和Person.defaultProps对Person的类型进行限制,这种写法写在了类定义的外部,是React示例中的写法,我们希望Person类具有内聚性,也就是这两个属性希望写在类的内部。改法如下

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <div id="test2"></div>
    <div id="test3"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写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: 18,
            }
            render() {
                console.log(this)
                const {name, age, sex} = this.props
                return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age+1}</li>
                    </ul>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Person name="tom" age={19} sex="" speak={speak}/>, document.getElementById('test1'))
        ReactDOM.render(<Person name="jerry" age={20} sex=""/>, document.getElementById('test2'))
        // ReactDOM.render(<Person name="lucy" age="21" sex="女"/>, document.getElementById('test3'))

        //另一种写法
        const p = {name:'lucy', age:21, sex:""}
        ReactDOM.render(<Person {...p}/>, document.getElementById('test3'))//原生js不允许使用展开运算符展开一个对象,但是react+babel就可以了。

        function speak() {
            console.log('我说话了');
        }
    </script>
</body>
</html>

 

3 有关构造器是否传递props的讨论

在react中文官网中,搜索constructor,找到其指导文档,文档中有如下描述

 

 

如果我们希望在构造函数中使用this.props属性,我们必须传递props给构造器,且传递给super()方法

如下示例。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Person extends React.Component {
            constructor(props) {
                //构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
                super(props);
                console.log('constructor中使用this.props:', this.props);
            }
            //对标签属性进行类型限制
            static propTypes = {
                name: PropTypes.string.isRequired,
            }
            static defaultProps = {
                sex: '不男不女',
                age: 18,
            }
            render() {
                const {name, age, sex} = this.props
                return (
                    <ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age+1}</li></ul>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Person name="tom"/>, document.getElementById('test1'))
    </script>
</body>
</html>

如果我们不传props,打印出undefined;我们传递props,打印出this.props的值。

其实,这种情况基本不存在,一般我们不会写构造器,即使写构造器,也不会在构造器中使用this.props。而且我们已经传递了props,为啥还需要使用this.props呢?

4 函数式组件使用props

我们讲,组件实例的三大属性:state,props和refs,都是对组件实例来说的。对于函数式组件,本身是一个函数,根本不存在实例,所以函数式组件不能使用这三个东西。但是有一个例外,就是props。函数式组件可以使用props属性,原因是函数式组件可以接收参数。如下所示:

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        function Person(props) {
            const {name, age, sex} = props
            return (
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age+1}</li>
                    </ul>
                )
        }
        //渲染组件到页面
        ReactDOM.render(<Person name="tom" sex="" age={19}/>, document.getElementById('test1'))
    </script>
</body>
</html>

函数式组件接收一个叫做props的参数,从父组件获取到props属性。

六、组件实例的三大核心属性-refs

1 字符串类型ref

组件的ref属性允许我们拿到该组件的一个真实dom节点,注意,拿到的是真实DOM节点,并非虚拟DOM节点。

该属性类似于原生html标签中的id属性。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Demo extends React.Component {
            showData = ()=>{
          console.log(this)
console.log(this.refs.input1);
alert(this.refs.input1.value);
            }
            showData2 = ()=>{
                console.log(this.refs.input2);
                alert(this.refs.input2.value);
            }
            render() {
                return (
                    <div>
                        <input ref="input1" type="text" palceholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                        <input ref="input2" onBlur={this.showData2} type="text" palceholder="失去焦点提示数据"/>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test1'))
    </script>
</body>
</html>

我们点击按钮后输出,我们看到,打印出this中包含的refs属性,以及this.refs.input1(一个真实DOM节点)

 

 

 2 回调形式的ref

根据react官网上的介绍,不推荐使用字符串形式的ref,会有性能问题。下面介绍回调形式的ref。回调形式的ref属性处更改为一个回调函数,比如input1,我们应该这样理解:把ref当前所处的节点挂在了实例自身上,并且取了个名字叫input1

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Demo extends React.Component {
            showData = ()=>{
                const {input1} = this
                alert(input1.value);
            }
            showData2 = ()=>{
                const {input2} = this
                alert(input2.value);
            }
            render() {
                return (
                    <div>
                        //把ref当前所处的节点挂在了实例自身上,并且取了个名字叫input1
                        <input ref={(currentNode)=>{this.input1 = currentNode}} type="text" palceholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                        <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" palceholder="失去焦点提示数据"/>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test1'))
    </script>
</body>
</html>

React遇到ref属性时,发现是一个回调函数,React就会执行这个回调函数,入参是当前节点,并在函数体中把当前节点挂在实例this上,取名叫input1。然后我们就可以在其他能触及到该实例this的地方使用input1了。比如showData函数中。

3 createRef形式

这种方式是React最推荐的方式

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Demo extends React.Component {
            /*
                React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
            */
           myRef = React.createRef()
           myRef2 = React.createRef()
            showData = ()=>{
                console.log(this.myRef)
                alert(this.myRef.current.value);
            }
            showData2 = ()=>{
                console.log(this.myRef2)
                alert(this.myRef2.current.value);
            }
            render() {
                return (
                    <div>
                        <input ref={this.myRef} type="text" palceholder="点击按钮提示数据"/>
                        <button onClick={this.showData}>点我提示左侧的数据</button>
                        <input onBlur={this.showData2} ref={this.myRef2} type="text" palceholder="失去焦点提示数据"/>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test1'))
    </script>
</body>
</html>

我们首先给实例添加一个 myRef = React.createRef() 属性,调用后可以返回一个容器,该容器可以保存被ref所标识的节点。

我们在input中 <input ref={this.myRef} 这种方式使用

七、React事件

1 引言

(1).通过OnXxx属性指定事件处理函数
  a.React使用的是自定义(合成)事件,而不是使用原生DOM事件---为了更好的兼容性
  b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)----为了高效
(2).通过event.target得到发生事件的DOM元素对象----不要过度使用ref
 

2 onXxx

当要操作的组件就是发生事件的事件源时,我们可以不使用ref属性,而使用onXxx传递event事件的方式操作组件。浏览器的事件机制规定,当事件发生时,传递给事件处理函数的第一个参数是事件对象。
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Demo extends React.Component {
            /*
                (1).通过OnXxx属性指定事件处理函数
                    a.React使用的是自定义(合成)事件,而不是使用原生DOM事件---为了更好的兼容性
                    b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)----为了高效
                (2).通过event.target得到发生事件的DOM元素对象----不要过度使用ref
            */
            showData = (event)=>{
                console.log(event.target)
                alert(event.target.value);
            }
            render() {
                return (
                    <div>
                        <input onBlur={this.showData} type="text" palceholder="失去焦点提示数据"/>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Demo/>, document.getElementById('test1'))
    </script>
</body>
</html>

 

2 非受控组件

原生form表单

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Login extends React.Component {
            render() {
                return (
                    <form action="http://meta.bgi.com">
                        用户名:<input type="text" name="username"/>
                        密码:<input type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Login/>, document.getElementById('test1'))
    </script>
</body>
</html>

action表示表单提交给哪个url处理,不写请求方式默认GET, <input type="text" name="username"/> 中的name为提交表单时的参数名

点击登录后页面跳转到meta.bgi.com并携带两个参数。

 

 

下面我们提一个需求,输入用户名和密码,点击登录,弹框显示输入的用户名和密码。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Login extends React.Component {
            handleSubmit = ()=>{
                const {username, password} = this;
                alert('用户名:${username.value},密码:${passwordvalue}')
            }
            render() {
                return (
                    <form action="http://meta.bgi.com" onSubmit={this.handleSubmit}>
                        用户名:<input ref={c => this.username=c} type="text" name="username"/>
                        密码:<input ref={c => this.password=c} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Login/>, document.getElementById('test1'))
    </script>
</body>
</html>

我们可以看到,当我们提交后,确实能弹框显示用户名和密码。

且x掉弹框后页面发生了跳转

 

 

 

 但是页面发生了跳转,我们希望页面不跳转不刷新,只弹框显示

我们去掉action <form action="http://meta.bgi.com" 。页面不跳转了,但是页面还是会刷新

 

我们使用event事件阻止表单提交,页面就不会刷新了。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Login extends React.Component {
            handleSubmit = (event)=>{
                event.preventDefault()
                const {username, password} = this;
                alert('用户名:'+username.value+',密码:'+password.value)
            }
            render() {
                return (
                    <form onSubmit={this.handleSubmit}>
                        用户名:<input ref={c => this.username=c} type="text" name="username"/>
                        密码:<input ref={c => this.password=c} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Login/>, document.getElementById('test1'))
    </script>
</body>
</html>

 

复习:原生html表单提交会自动触发页面刷新,如果指定了action页面还会跳转。以上示例是为了复习我们原生表单的特性。

 

上面我们就写了一个非受控组件。组件的状态不受React控制,现用现取。

 

3 受控组件

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Login extends React.Component {
            state = {
                username:'',
                password:'',
            }
            saveUsername = (event)=>{
                this.setState({username:event.target.value})
            }
            savePassword = (event)=>{
                this.setState({password:event.target.value})
            }
            handleSubmit = (event)=>{
                event.preventDefault()
                const {username, password} = this.state;
                alert('用户名:'+username+',密码:'+password)
            }
            render() {
                return (
                    <form onSubmit={this.handleSubmit}>
                        用户名:<input onChange={this.saveUsername} type="text" name="username"/>
                        密码:<input onChange={this.savePassword} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Login/>, document.getElementById('test1'))
    </script>
</body>
</html>

我们修改<input>元素,去掉ref属性,添加onChange属性, <input onChange={this.saveUsername} 然后定义了两个函数,然后在函数中修改state this.setState({username:event.target.value}) 。然后在表单提交的时候,我们直接取state中的username。就是受控组件。也就是说组件的状态受React控制。

 

4 对上面的优化

对象基本知识复习

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <script type="text/javascript">
        let a = 'name'
        let obj = {}
        // obj.name = 'tom'
        obj[a] = 'tom'
        console.log(obj)
    </script>
</body>
</html>

我们想得到一个{'name':'tom'}的对象,我们可以使用[]的形式获取变量的值。 obj[a] = 'tom' 。

 

接下来我们对上例进行优化

 我们发现saveUsername和savePassword函数高度重合,我们可以整合成一个

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Login extends React.Component {
            state = {
                username:'',
                password:'',
            }
            saveFormdata = (dataType)=>{
                return (event)=>{
                    this.setState({[dataType]:event.target.value})
                }
            }
            handleSubmit = (event)=>{
                event.preventDefault()
                const {username, password} = this.state;
                alert('用户名:'+username+',密码:'+password)
            }
            render() {
                return (
                    <form onSubmit={this.handleSubmit}>
                        用户名:<input onChange={this.saveFormdata("username")} type="text" name="username"/>
                        密码:<input onChange={this.saveFormdata("password")} type="password" name="password"/>
                        <button>登录</button>
                    </form>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Login/>, document.getElementById('test1'))
    </script>
</body>
</html>

上面有个变动点,就是 <input onChange={this.saveUsername} 改为了 <input onChange={this.saveFormdata("username")} 。

我们要注意,onChange是一个回调函数,所以修改前 <input onChange={this.saveUsername} 不能加(),加上() <input onChange={this.saveUsername()} 就变成onChange为函数的返回值了,而 this.saveUsername() 返回值是undefined,执行肯定会报错。而 <input onChange={this.saveFormdata("username")} 则不同,其函数的返回值还是一个函数,所以可以这样写。并且React帮我们把event传递到函数内的回调函数中。

5 高阶函数和函数的柯里化

5.1高阶函数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数

1 若A函数,接收的参数是一个函数,那么该函数就是高阶函数

2 若A函数,调用的返回值依然是一个函数,那么A就是高阶函数

常见的高阶函数:Promise、setTimeout、arr.map()等等

5.2函数的柯里化

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

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <script type="text/javascript">
        //不使用柯里化
        // function sum(a,b,c) {
        //     return a+b+c;
        // }
        // const result = sum(1,2,3)
        //使用柯里化
        function sum(a) {
            return (b)=>{
                return (c)=>{
                    return a+b+c
                }
            }
        }
        const result = sum(1)(2)(3)
        console.log(result)
    </script>
</body>
</html>

上例中如下部分其实就是用的函数的柯里化

            saveFormdata = (dataType)=>{
                return (event)=>{
                    this.setState({[dataType]:event.target.value})
                }
            }

在调用的地方

<input onChange={this.saveFormdata("username")}

我们首先传递了dataType到函数中,然后React生成一个事件event,然后再将event作为参数传递给内层函数,然后在内层函数中对dataType和event做了统一的处理。

差不多类似于这样调用 this.saveFormdata("username")(event) ,只不过event是React生成的事件,我们接触不到,柯里化调用 this.saveFormdata("username")(event) 也是在React内部实现的。

八、组件的生命周期

1 常用组件生命周期

组件从创建到死亡会经历一些特定的阶段

React组件中包含一系列钩子函数(生命周期回调函数),会在特定的时刻调用

我们在定义组件时,会在特定的生命周期回调函数中做特定的工作。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Login extends React.Component {
            state = {opacity:1,}
            //组件挂载完毕
            componentDidMount(){
                this.timer = setInterval(() => {
                    let {opacity} = this.state
                    opacity-=0.1
                    if(opacity <= 0) opacity = 1
                    this.setState({opacity})
                }, 200);
            }
            //组件将要卸载时
            componentWillUnmount(){
                //清除定时器
                clearInterval(this.timer)
            }
            death = ()=> {
                //卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
            }
            //初始化渲染、状态更新之后
            render() {
                return (
                    <div>
                        <h2 style={{opacity:this.state.opacity}}>React学不会怎么办?</h2>
                        <button onClick={this.death}>不活了</button>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Login/>, document.getElementById('test1'))
    </script>
</body>
</html>

2 组件声明周期演示图

 

 

3 全部组件生命周期

初始化阶段:由组件render触发

  constructor

  componentWillMount

  render

  componentDidMount

更新阶段:由组件内部this.setState触发或父组件render触发

  shoudComponentUpdate

  componentWillUpdate

  render

  componentDidUpdate

卸载组件:

  componentWillUnmount

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Count extends React.Component {
            //构造器
            constructor(props){
                console.log('Count---constructor')
                super(props)
                this.state = {count:0}
            }
            //组件将要挂载的钩子
            componentWillMount(){
                console.log('Count---componentWillMount')
            }
            //组件挂载完毕
            componentDidMount(){
                console.log('Count---componentDidMount')
            }
            //组件将要卸载时
            componentWillUnmount(){
                console.log('Count---componentWillUnmount')
            }
            //控制组件更新的阀门
            shoudComponentUpdate(){
                console.log('Count---shoudComponentUpdate')
                return true
            }
            //组件将要更新的钩子
            componentWillUpdate(){
                console.log('Count---componentWillUpdate')
            }
            //组件更新完毕的钩子
            componentDidUpdate(){
                console.log('Count---componentDidUpdate')
            }
            //加1按钮的回调
            add = ()=> {
                const {count} = this.state
                this.setState({count:count+1})
            }
            //卸载组件按钮的回调
            death = ()=> {
                //卸载组件
                ReactDOM.unmountComponentAtNode(document.getElementById('test1'))
            }
            //强制更新按钮的回调
            force = ()=> {
                this.forceUpdate()
            }
            //初始化渲染、状态更新之后
            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>
                )
            }
        }
        class A extends React.Component {
            state = {carName:'奔驰'}
            changeCar = ()=>{
                this.setState({carName:'奥拓'})
            }
            render() {
                return (
                    <div>
                        <div>我是A组件</div>
                        <button onClick={this.changeCar}>换车</button>
                        <B carName={this.state.carName}/>
                    </div>
                );
            }
        }
        class B extends React.Component {
            componentWillReceiveProps() {
                console.log('B---componentWillReceiveProps')
            }
            render() {
                return (
                    <div>
                        我是B组件,接收到的车是:{this.props.carName}
                    </div>
                );
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Count/>, document.getElementById('test1'))
        // ReactDOM.render(<A/>, document.getElementById('test1'))
    </script>
</body>
</html>

九、Diffing算法

1 Diffing算法

React维护了一个虚拟DOM,在页面渲染时,React会把虚拟DOM中的每个标签和真实DOM进行比较,有变化才更新,没变化不更新,这就是React中的Diffing算法。下面我们写一个简单的示例演示Diffing。

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        class Time extends React.Component {
            state = {date:new Date()}
            componentDidMount() {
                setInterval(() => {
                    this.setState({date:new Date()})
                }, 1000)
            }
            render() {
                return (
                    <div>
                        <h1>hello</h1>
                        <input type='text'/>
                        <span>现在是:{this.state.date.toTimeString()}</span>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Time/>, document.getElementById('test1'))
    </script>
</body>
</html>

我们会看到页面上的时间一直在更新,而我们在输入框中输入内容,如果输入框这个标签有更新的话,其内容必将消失,但是我们看到的是输入框中的内容一直不变,这就说明了Diffing算法确实起作用了。

 

 

 

 

2 虚拟DOM中的key

虚拟DOM中key的作用:

1)简单的说,key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用

2)详细的说:当状态数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】和【旧虚拟DOM】的diff比较,比较规则如下:

  a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:

    (1)若虚拟DOM中内容没变,直接使用之前的真实DOM

    (2)若虚拟DOM中的内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM

  b. 旧虚拟DOM中未找到与新虚拟DOM相同的key

    根据数据创建新的真实DOM,随后渲染到页面。

 

下面例子分别演示了使用index索引作为key和使用id作为key的比较

<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>hello_react</title></head>
<body>
    <div id="test1"></div>
    <!--引入react核心库-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!--引入react-dom,用于支持react操作DOM-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!--引入babel,用于将jsx转换为js-->
    <script type="text/javascript" src="../js/babel.js"></script>
    <!--引入prop-types,用于对组件标签属性进行限制-->
    <script type="text/javascript" src="../js/prop-types.js"></script>
    <script type="text/babel"> /*此处一定要写babel*/
        /**
         慢动作回放----使用index索引值作为key
            数据:
                {id:1,name:'小张',age:18},
                {id:2,name:'小李',age:19},
            初始的虚拟DOM:
                <li key=0>小张---18</li>
                <li key=1>小李---19</li>
            更新后的数据:
                <li key=3>小王---20</li>
                <li key=0>小张---18</li>
                <li key=1>小李---19</li>
            更新树后的虚拟DOM:
                <li key=0>小王---20</li>
                <li key=1>小张---18</li>
                <li key=2>小李---19</li>

------------------------------------------------------------

         慢动作回放----使用id唯一标识索引值作为key
            数据:
                {id:1,name:'小张',age:18},
                {id:2,name:'小李',age:19},
            初始的虚拟DOM:
                <li key=1>小张---18</li>
                <li key=2>小李---19</li>
            更新后的数据:
                <li key=3>小王---20</li>
                <li key=1>小张---18</li>
                <li key=2>小李---19</li>
            更新树后的虚拟DOM:
                <li key=3>小王---20</li>
                <li key=1>小张---18</li>
                <li key=2>小李---19</li>

        */
        class Person extends React.Component {
            state = {
                persons: [
                    {id:1,name:'小张',age:18},
                    {id:2,name:'小李',age:19},
                ]
            }
            add = ()=>{
                const {persons} = this.state
                const p = {id:3,name:'小王',age:20}
                this.setState({persons:[p,...persons]})
            }
            render() {
                return (
                    <div>
                        <h2>展示人员信息</h2>
                        <h3>使用index索引值作为key</h3>
                        <button onClick={this.add}>添加一个小王</button>
                        <ul>
                            {
                                this.state.persons.map((personObj, index)=>{
                                    return <li key={index}>{personObj.name}---{personObj.age}</li>
                                })
                            }
                        </ul>
                        <hr/>
                        <hr/>
                        <h3>使用id(数据的唯一标识)索引值作为key</h3>
                        <ul>
                            {
                                this.state.persons.map((personObj)=>{
                                    return <li key={personObj.id}>{personObj.name}---{personObj.age}</li>
                                })
                            }
                        </ul>
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Person/>, document.getElementById('test1'))
    </script>
</body>
</html>

 

以上功能相同,但是其性能则相差很大。

有些同学说,我管他性能好坏,我实现功能就行了,这样确实没毛病,但是下面提一个新的需求,则我们不得不考虑两种方式的区别了。

我们在上面例子中<li>组件内添加一个<input>组件 <input type="text"/></li> 。然后我们做如下截图中的操作。

 发现,数据错乱了。由此看出,由什么作为虚拟DOM的key还是很重要的。新旧虚拟DOM进行比较的时候,依据的就是虚拟DOM的key,如果使用了不得当的key,会带来很大的性能问题,而且有时候会发生如上图这种比较严重的数据错乱问题。

十、React脚手架

1 React脚手架

1 xxx脚手架:用来帮助程序员快速创建一个基于xxx库的模板项目。包含了所有需要的配置,下载好了相关依赖

React提供的脚手架:create-react-app

2 创建过程

第一步:全局安装create-react-app。npm install -g create-react-app。要使用脚手架,首先这个脚手架要存在,这一步就是安装这个脚手架。

第二步:切换到想创建项目的目录,使用命令npx create-react-app 项目名创建一个项目

第三步:进入项目文件夹,启动项目,npm start

 

2 使用create-react-app快速创建一个项目

npx create-react-app wechat --template typescript

 

如果提示A template was not provided,说明create-react-app太旧,需要首先卸载,重新安装

npm uninstall -g create-react-app

查看是否卸载

$ which create-react-app
/e/nodejs/node_global/create-react-app

如果没有卸载干净,则

rm -rf /c/Users/songzhenjing/AppData/Roaming/npm/create-react-app

重新执行

npx create-react-app wechat --template typescript

看到如下界面说明安装成功

我们启动项目看一下结果,启动命令都放在package.json文件里面了。我们进入到wechat目录,查看一下这个文件

  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },

执行npm run start启动项目,启动完毕,浏览器将会自动为我们打开如下这个页面

 

 至此,脚手架创建React项目成功。

十一、React组件编程实例

1 效果

我们要做的实例效果如下:

上面一个输入框,当我们按回车时可以添加一个待完成任务,

中间列表展示待完成的任务,且鼠标进入背景色深色显示;每一行有个删除按钮

下面展示已完成/全部。且可以支持全选和取消全选。且有一个按钮"删除全部已完成"

 

 

 2 组件拆分

我们可以把页面拆分为3个组件,头部Header,中间List,下部Footer。其中中间每一行是一个组件Item

3 代码实现

该用例使用的技术和js相关的基础知识对于初学者非常有帮助,如果可能的话,最好参照实例手敲一遍代码。

项目结构

3.1 index.html

<!doctype html>
<html lang="en">
  <head>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

3.2 index.js

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

ReactDOM.render(<App/>, document.getElementById('root'));

index.css

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
}

 

3.3 App组件

需要注意的问题:

1 中间列表组件List展示的数据,我们直接想的话,展示的数据要放到List组件中,但是,Footer组件需要获取List组件的状态用于展示数目,所以我们不能把展示的数据放到List中,应该放到父组件App中,我们通过props属性把数据传递给List和Footer组件。

2 同时,Header中添加一条数据的时候,这条数据要展示到List组件中,而List组件的数据在其父组件App中,所以我们要想办法把Header输入框中的数据传递给App组件没然后由App组件更新state,进而触发子组件List重新渲染,使新数据展示在List中。此处我们用的方式是回调函数(或者叫高阶函数)的方式把Header中的数据传递给父组件App。

import React, { Component } from 'react';
import PropTypes from 'prop-types'
import './App.css'
import Footer from './components/Footer/Footer';
import Header from './components/Header/Header';
import List from './components/List/List';

class App extends Component {
  static propTypes = {
    addTodo:PropTypes.func.isRequired
  }

  state = {
    todos: [
      {id:'001', name:'吃饭', done:true},
      {id:'002', name:'睡觉', done:true},
      {id:'003', name:'打代码', done:false},
    ]
  }

  //用于添加一个todo,接收的参数是todo对象
  addTodo = (todoObj)=>{
    const {todos} = this.state
    const newTodos = [todoObj,...todos]
    this.setState({todos:newTodos})
  }

  //
  updateTodo = (id,done)=>{
    const {todos} = this.state
    const newTodos = todos.map((todoObj)=>{
      if(todoObj.id === id) return {...todoObj,done:done}
      else return todoObj
    })
    this.setState({todos:newTodos})
  }

  //用于删除一个todo,接收的参数是id
  deleteTodo = (id)=>{
    const {todos} = this.state
    const newTodos = todos.filter((todoObj)=>{
      return todoObj.id !== id
    })
    this.setState({todos:newTodos})
  }

  //用于
  checkAllTodo = (done)=>{
    const {todos} = this.state
    const newTodos = todos.map((todoObj)=>{
      return {...todoObj,done:done}
    })
    this.setState({todos:newTodos})
  }

  //清除所有已经完成的
  clearAllDone = ()=>{
    const {todos} = this.state
    const newTodos = todos.filter((todoObj)=>{
      return !todoObj.done
    })
    this.setState({todos:newTodos})
  }

  render() {
    return (
      <div className="todo-container">
        <div className="todo-wrap">
          <Header addTodo={this.addTodo}/>
          <List todos={this.state.todos} updateTodo={this.updateTodo} deleteTodo={this.deleteTodo}/>
          <Footer todos={this.state.todos} checkAllTodo={this.checkAllTodo} clearAllDone={this.clearAllDone}/>
        </div>
      </div>
    );
  }
}

export default App;

App.css

body {
  background: #fff;
}

.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}

.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}

.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}

.btn:focus {
  outline: none;
}

.todo-container {
  width: 600px;
  margin: 0 auto;
}

.todo-container .todo-warp {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}

 

3.4 List组件

import React, { Component } from 'react';
import PropTypes from 'prop-types'
import Item from '../Item/Item'
import './List.css'

export default class List extends Component {
    static propTypes = {
        todos:PropTypes.array.isRequired,
        updateTodo:PropTypes.func.isRequired,
        deleteTodo:PropTypes.func.isRequired
    }
    render() {
        const {todos, updateTodo, deleteTodo} = this.props
        return (
            <ul className="todo-main">
                {
                    todos.map((todo)=>{
                        return <Item key={todo.id} {...todo} updateTodo={updateTodo} deleteTodo={deleteTodo}/>
                    })
                }
            </ul>
        );
    }
}

List.css

.todo-main {
    margin-left: 0px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding: 0px;
}

.todo-empty {
    height: 40px;
    line-height: 40px;
    border: 1px solid #ddd;
    border-radius: 2px;
    padding-left: 5px;
    margin-top: 10px;
}

 

3.5 Item组件

import React, { Component } from 'react';
import './Item.css'

export default class Item extends Component {
    state = {mouse:false}
    handleMouse = (flag)=>{
        return ()=>{
            this.setState({mouse:flag})
        }
    }
    //勾选取消勾选
    handleCheck = (id)=>{
        return (event)=>{
            this.props.updateTodo(id,event.target.checked)
        }
    }
    //删除按钮
    handleDelete = (id)=>{
        if(window.confirm('确定删除吗?')) {
            this.props.deleteTodo(id)
        }
    }
    render() {
        const {id,name,done} = this.props
        const {mouse} = this.state
        return (
            <li style={{backgroundColor:mouse ? '#ddd' : 'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}>
                <label>
                    <input type="checkbox" checked={done} onChange={this.handleCheck(id)}/>
                    <span>{name}</span>
                </label>
                <button onClick={()=>{this.handleDelete(id)}} className="btn btn-danger" style={{display:mouse ? 'block' : 'none'}}>删除</button>
            </li>
        );
    }
}

Item.css

li {
    list-style: none;
    height:36px;
    line-height: 36px;
    padding:0 5px;
    border-bottom: 1px solid #ddd;
}

li label {
    float: left;
    cursor: pointer;
}

li label li input {
    vertical-align: middle;
    margin-right: 6px;
    position: relative;
    top: -1px;
}

li button {
    float: right;
    display: none;
    margin-top: 3px;
}

li:before {
    content: initial;
}

li:last-child {
    border-bottom: none;
}

 

3.6 Header组件

import React, { Component } from 'react';
// import {nanoid} from 'nanoid'
import './Header.css'

export default class Header extends Component {
    handleKeyUp =(e)=>{
        //判断是否是回车
        if(e.keyCode !== 13) return
        if(e.target.value.trim() === '') {alert('输入不能为空');return}
        console.log(e.target.value)
        //准备好一个todo对象
        const todoObj = {id:new Date(),name:e.target.value,done:false}
        //传递给父组件
        this.props.addTodo(todoObj)
        // this.target.value = ''
    }
    render() {
        return (
            <div className="todo-header">
                <input onKeyUp={this.handleKeyUp} type="text" placeholder="请输入你的任务名称,按回车键确认"></input>
            </div>
        );
    }
}

Header.css

.todo-header input {
    width: 560px;
    height: 28px;
    font-size: 14px;
    border: 1px solid #ccc;
    border-radius: 4px;
    padding: 4px 7px;
  }
  
  .todo-header input:focus {
    outline: none;
    border-color: rgba(82, 168, 236, 0.8);
    box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
  }

 

3.7 Footer组件

import React, { Component } from 'react';
import './Footer.css'

export default class Footer extends Component {

    handleCheckAll=(event)=>{
        this.props.checkAllTodo(event.target.checked)
    }

    handleClearAllDone=()=>{
        this.props.clearAllDone()
    }

    render() {
        const {todos} = this.props
        //计算已完成的
        const doneCount = todos.reduce((pre,todo)=>{return pre+(todo.done ? 1 : 0)},0)
        //总数
        const total = todos.length
        return (
            <div className="todo-footer">
                <label>
                    <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount===total && total !== 0 ? true:false}/>
                </label>
                <span>
                    <span>已完成{doneCount}</span> / 全部{total}
                </span>
                <button onClick={this.handleClearAllDone} className="btn btn-danger">清除已完成任务</button>
            </div>
        );
    }
}

Footer.css

.todo-footer {
    height: 40px;
    line-height: 40px;
    padding-left: 6px;
    margin-top: 5px;
}

.todo-footer label {
    display: inline-block;
    margin-right: 20px;
    cursor: pointer;
}

.todo-footer label input {
    position: relative;
    top: -1px;
    vertical-align: middle;
    margin-right: 5px;
}

.todo-footer button {
    float: right;
    margin-top: 5px;
}

 

4 成果演示

 

5 总结

1 如何确定将数据放到哪个组件的state中?

  某个组件使用:放在其自身的state中

  某些组件使用:放到他们共同的父组件state中(官方称此操作为:状态提升)

2 关于父子之间的通信

  【父组件】给【子组件】专递数据:通过props传递

  【子组件】给【父组件】传递数据:通过props传递,要求父组件提前给子组件传递一个函数

3 注意defaultChecked和checked的区别

4 状态在哪里,操作状态的方法就在哪里

 

代码后面会上传到github,reactstudy/src_todos

十二、使用express搭建简单的nodejs webserver

1 express简介

Express是一个简洁而灵活的node.js Web应用框架。

 

2 搭建过程

2.1 安装express

全局安装express和express-generator,express-generator是express脚手架,能够为我们快速构建一个express项目。

$ npm install express --save -g
$ npm install express-generator --save -g

 

 

 

 2.2 创建项目

使用express命令创建项目,项目名叫node_server

 

 

安装依赖

cd node_server,执行cnpm install

启动服务

npm run server

 

 

页面访问,看到如下页面说明服务搭建成功。

 

3 项目分析

项目目录结构

 

 bin/www,用于项目启动,里面可以配置项目端口号之类的东西

app.js,main文件,请求首先到达这里,在这里可以配置路由转发

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
app.use('/', indexRouter);
app.use('/users', usersRouter);

上面的代码说明了users开头的请求交由usersRouter处理,其余请求交由indexRouter处理。

我们看一下routes/index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

module.exports = router;

上面代码收到/请求后,交由views/index.jade渲染页面,传递的参数是title:Express

我们看一下index.jade

extends layout

block content
  h1= title
  p Welcome to #{title}

在这里面,定义了页面元素h1和p要显示的内容。

 

4 添加一个接口

我们在项目中添加一个/student接口,返回一个包含学生信息的json数据

修改routes/index.js,添加如下接口

router.get('/student',(request,response)=>{
    const students = [
        {id:'001',name:'tom',age:18},
        {id:'002',name:'jerry',age:19},
        {id:'003',name:'tony',age:20},
    ]
    response.send(students)
})

,重启服务,查看页面:

十三、使用axios请求后台和配置代理

下面我们写一个例子,用户点击按钮请求后台获取学生信息,请求的url就是上一篇中的localhost:5000/student

1 在packages.json中配置代理

我们用create-react-app脚手架新建一个项目

下载axios依赖

cnpm install axios

修改App.js

import React, { Component } from 'react';
import './App.css'
import axios from 'axios'

class App extends Component {
  getStudentData = ()=>{
    axios.get('http://localhost:3000/student').then(
      response => {console.log('成功了',response.data)},
      error => {console.log('失败了',error)}
    )
  }
  render() {
    return (
      <div>
        <button onClick={this.getStudentData}>点我获取学生数据</button>
      </div>
    );
  }
}

export default App;

 

然后,修改package.json,添加:

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

,然后启动上一节中的express后台。并启动本项目,

 

 

 2 setupProxy.js配置多个代理

我们删除package.json中配置的代理。

在src目录下新建setupProxy.js文件

const proxy = require('http-proxy-middleware');

module.exports = function(app) {
    app.use(
        proxy('/api1',{
            target: 'http://localhost:5000',
            changeOrigin:true,
            pathRewrite:{'^/api1':''}
        }),
        proxy('/api2',{
            target: 'http://localhost:5001',
            changeOrigin:true,
            pathRewrite:{'^/api2':''}
        }),
    )
}

修改App.js

import React, { Component } from 'react';
import './App.css'
import axios from 'axios'

class App extends Component {
  getStudentData = ()=>{
    axios.get('http://localhost:3000/api1/student').then(
      response => {console.log('成功了',response.data)},
      error => {console.log('失败了',error)}
    )
  }
  getCarData = ()=>{
    axios.get('http://localhost:3000/api2/car').then(
      response => {console.log('成功了',response.data)},
      error => {console.log('失败了',error)}
    )
  }
  render() {
    return (
      <div>
        <button onClick={this.getStudentData}>点我获取学生数据</button>
        <button onClick={this.getCarData}>点我获取汽车数据</button>
      </div>
    );
  }
}

export default App;

当请求以/api1开头则代理把请求转发给localhost:5000

当请求以/api2开头则代理把请求转发给localhost:5001

但是运行后,页面一直报 GET http://localhost:3000/api1/student 404 (Not Found) 。暂时未解决。

十四、fetch方式发送http请求获取数据

1 Promise语法

Promise语法符合“关注分离”原则,即把一个复杂的事情,分离出几个阶段,每一个阶段都会返回一个响应,前一个响应成功之后才进行下一阶段。

例子:比如我想让小明去打瓶酱油,第一步,小明出了小区首先看一下商店是否开门,然后小明给我一个Response说开门,然后第二步,小明到达商店,询问店员是否有酱油,店员说只有老抽,没有生抽,然后小明再给我一个Response,问我只有老抽买不买,我说买。第三步小明提着一瓶老抽回来了。

详细介绍待补充。

 

2 fetch介绍

传统我们使用xhr(XmlHttpResquest)发送请求。其实浏览器还自带另一种Promise风格的请求方式fetch。

例子:

我们修改 十三、使用axios请求后台和配置代理 中的实例,改为使用fetch方式发送请求

import React, { Component } from 'react';

class App extends Component {
    getStudentData = ()=>{
    // axios.get('http://localhost:3000/student').then(
    //   response => {console.log('成功了',response.data)},
    //   error => {console.log('失败了',error)}
    // )
    fetch('http://localhost:3000/student').then(
      response => {
        console.log('联系服务器成功了', response)
        return response.json()
      },
      error => {
        console.log('联系服务器失败了',error)
        return new Promise(()=>{})
      }
    ).then(
      response => {
        console.log('获取数据成功了',response)
      },
      error => {console.log('获取数据失败了',error)}
    )
  }
  render() {
    return (
      <div>
        <button onClick={this.getStudentData}>点我获取学生数据</button>
      </div>
    );
  }
}

export default App;

第一个then返回一个Promise对象,第二个then返回真正的数据。

 

packages.json中的配置

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

效果展示:

 

 

十五、React路由

1 React路由原理

1.1 单页面应用

传统的网页,由一堆html组成,我们点击其中一个链接,页面在各个html之间来回切换,伴随着整个页面的刷新。

而React单页面应用(SPA)指的是,我们点击链接时,地址栏中的url发生了变化,但是页面并不会整个刷新,只刷新部分页面。React单页面是通过路由实现的,也就是说当点击某个链接时,地址栏中的url发生变化,React路由监控机制监控到这个变化,查询路由表来确定显示哪个组件。

 

1.2 前端路由的基石-history

下面一个例子,我们演示React是如何做到修改地址栏地址而不引起页面刷新的,进而我们可以猜测到React路由的实现原理。代码中我们使用了 history.js 来操作浏览器的history,这样可以屏蔽原生history API的复杂性

<html>
<body>
    <a href="http://www.baidu.com" onclick="return push('/test1')">push test1</a><br><br>
    <button onclick="push('/test2')">push test2</button><br><br>
    <button onclick="replace('/test3')">replace test3</button><br><br>
    <button onclick="back()">回退</button><br><br>
    <button onclick="forward()">前进</button><br><br>
    <script type="text/javascript" src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
    <script>
        let history = History.createBrowserHistory()
        function push(path) {
            history.push(path)
            return false
        }
        function replace(path) {
            history.replace(path)
        }
        function back() {
            history.goBack()
        }
        function forward() {
            history.goForward()
        }
        history.listen((location)=>{
            console.log('请求路由路径变化了', location)
        })
    </script>
</body>
</html>

注意下图中地址栏的变化

 浏览器中history是一个栈,浏览器当前页面显示的是栈顶的地址。当我们把一个地址push进栈顶时,浏览器显示就会发生变化。React就是采用浏览器的history特性,当地址栏path发生变化时,React监控到这一变化,显示不同的组件。

 

2 实例

 我们写一个实例,左边导航栏有导航菜单,右面主面板。根据导航菜单的不同主面板显示不同的组件。

在这里我们用到了bootstrap.css

 

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter} from 'react-router-dom'
import App from './App';

ReactDOM.render(
    <BrowserRouter>
        <App/>
    </BrowserRouter>,
    document.getElementById('root')
);

路由器:BrowserRouter,负责监控地址栏path的变化,当path变化时,查找路由表显示相应的组件

App.js

import React, { Component } from 'react';
import {Route, Routes, Link,} from 'react-router-dom'
import About from "./components/About";
import Home from "./components/Home";
import "../public/bootstrap.css"

class App extends Component {
  render() {
    return (
      <div>
        <div className="row">
          <div className="col-xs-offset-2 col-xs-8">
            <div className="page-header"><h2>React Router Demo</h2></div>
          </div>
        </div>
          <div className="row">
            <div className="col-xs-2 col-xs-offset-2">
              <div className="list-group">
                {/*原生html中,靠<a>跳转不同的页面*/}
                {/*<a className="list-group-item" href="./about.html">About</a>*/}
                {/*<a className="list-group-item active" href="./home.html">Home</a>*/}

                {/* 在React中靠路由链接实现切换组件---编写路由链接 */}
                <Link className="list-group-item" to="/about">About</Link>
                <Link className="list-group-item" to="/home">Home</Link>
              </div>
            </div>
            <div className="col-xs-6">
              <div className="panel">
                <div className="panel-body">
                  {/*  注册路由*/}
                  <Routes><Route path="/about" element={<About/>}/>
                    <Route path="/home" element={<Home/>}/>
                  </Routes>
                </div>
              </div>
            </div>
          </div>
      </div>
    );
  }
}

export default App;

路由链接:Link,来自于react-route-dom。

 <Link className="list-group-item" to="/about">About</Link> 

注册路由:

<Routes>
    <Route path="/about" element={<About/>}/>
     <Route path="/home" element={<Home/>}/>
</Routes>

About.jsx

import React, {Component} from 'react';

class About extends Component {
    render() {
        return (
            <h3>我是About内容</h3>
        );
    }
}
export default About;

Home.jsx

 

import React, {Component} from 'react';

class Home extends Component {
    render() {
        return (
            <h3>我是Home内容</h3>
        );
    }
}

export default Home;

 

演示(注意地址栏的变化,和页面是否有刷新)

 

3 HashRouter

上面我们使用的是BrowserRouter,下面我们使用HashRouter

我们把index.js中的BrowserRouter改为HashRouter

import React from 'react';
import ReactDOM from 'react-dom';
import {HashRouter} from 'react-router-dom'
import App from './App';

ReactDOM.render(
    <HashRouter>
        <App/>
    </HashRouter>,
    document.getElementById('root')
);

效果,

BrowersRouter和HashRouter的区别在地址栏中,HashRouter带有#,在提交数据时#后面的内容不会被传递

 

 

接下来P85

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 
posted @ 2021-12-12 12:15  zhenjingcool  阅读(79)  评论(0编辑  收藏  举报