React学习笔记01-React-JSX

young 545 2022-09-30

React特点

声明式编程

  • 声明式编程是目前整个大前端开发的模式:Vue、React、Flutter、SwiftUI
  • 它允许我们只需要维护自己的状态,当状态改变时,React可以根据最新的状态去渲染我们的UI界面

组件化开发

  • 组件化开发页面是目前前端的流行趋势,我们会将复杂的页面拆分成一个个小的组件
  • 如何合理的进行组件的划分和设计也是重点

多平台适配

  • React主要是用于开发Web页面
  • ReactNative用于开发移动端跨平台
  • ReactVR用于开发虚拟现实Web应用

案例

在页面显示一个文本 Hello World

点击下方的一个按钮,点击之后文本改变为Hello React

原生代码实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<h2 class="title"></h2>
<button class="btn">改变文本</button>

<script>
    // 命令式编程
    // 1.定义数据
    let message = "hello world"

    // 2.将数据显示在h2元素中
    const titleEl = document.getElementsByClassName("title")[0]
    titleEl.innerHTML = message

    // 3.点击按钮,界面数据发生改变
    const btnEl = document.getElementsByClassName("btn")[0]
    btnEl.addEventListener("click", e => {
        message = "hello react"
        titleEl.innerHTML = message
    })
</script>
</body>
</html>

React开发

React开发依赖

  • 必须依赖三个库
    • react:包含react所必须的核心代码
    • react-dom:react渲染在不同平台所需的核心代码
    • babel:将jsx转换成React代码的工具
  • Vue只需要一个Vue.js文件即可,但是React需要依赖三个库
    • 0.14版本之前没有react-dom,所有功能都包含的在react中
    • 这三个库各司其职
    • 拆分的原因:react-native
    • react中包含了react和react-native所共有的核心代码
  • react-dom针对web和native所完成的事情不同
    • web端:react-dom会将jsx最终渲染成真实的DOM,显示在浏览器中
    • native端:react-dom会将jsx最终渲染成原生的控件(如Android中的Button,IOS中的UIButton)

Babel

  • Babel是什么
    • Babel又名Babel.js
    • 是目前前端使用非常广泛的编译器、转移器
    • 当下很多浏览器不支持ES6语法,但是ES6的语法非常的简洁方便
    • 编写源码时可以使用ES6来编写,之后通过Babel工具将ES6转换成大多数浏览器都支持的ES5语法
  • React和Babel的关系
    • 默认情况下开发React可以不使用babel
    • 自己需要写React.createElement来编写代码,非常的繁琐,可读性也非常差
    • 所以我们可以直接编写jsx(JavaScript XML)的语法,通过Babel来转换成React.createElement

引入React依赖

  • 添加依赖的三种方式
    • 直接通过CDN引入
    • 下载到本地,添加本地依赖
    • 通过npm管理

暂时可以通过CDN进行引入

<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

这里的crossorigin属性,是为了拿到跨域脚本的错误信息

用React实现Hello World

  • 使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel
  • ReactDOM.render
    • 参数一:要渲染的内容,可以是HTML元素,也可以是React组件
    • 参数二:要将渲染分内容挂载到哪一个HTML元素上
  • 通过{}语法来引入外部的变量或者表达式,在Vue中使用的{{}},需要注意区分
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <!-- 添加React依赖 -->
    <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
    <div id="app"></div>
    <!-- 开始开发 -->
    <!-- 注意事项:使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel -->
    <script type="text/babel">
        // ReactDOM.render(渲染的内容, 挂载的对象)
        let message = "Hello World"
        // Vue中是{{}},React中是{}
        ReactDOM.render(<h2>{message}</h2>,document.getElementById('app'))
    </script>
</body>
</html>

添加按钮,实现文本改变

错误语法

jsx特点:多个组件最外层(根)只能有一个组件

ReactDOM.render(
    <h2>{message}</h2>
    <button>改变文本</button>,
    document.getElementById('app'))

正确语法

ReactDOM.render(
    <div>
        <h2>{message}</h2>
        <button>改变文本</button>
    </div>,
    document.getElementById('app'))

添加点击事件

创建点击触发事件function

function btnClick() {
        console.log("按钮发生点击")
        message = "Hello React"        
    }

绑定点击事件

ReactDOM.render(
    <div>
        <h2>{message}</h2>
        <button onClick={btnClick}>改变文本</button>
    </div>,
    document.getElementById('app'))

点击页面上的按钮,发现控制台有日志打印,但是页面的文本并没有改变

在Vue中,绑定的元素发生改变,页面数据会立即跟着改变,但是在React中,需要重新渲染DOM

最终代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 添加React依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<div id="app"></div>
<!-- 开始开发 -->
<!-- 注意事项:使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel -->
<!-- jsx特点:多个组件最外层(根)只能有一个组件 -->
<script type="text/babel">
    // ReactDOM.render(渲染的内容, 挂载的对象)
    let message = "Hello World"
    // Vue中是{{}},React中是{}
    // 会报错 多个组件最外层(根)只能有一个组件
    /*ReactDOM.render(
        <h2>{message}</h2>
        <button>改变文本</button>,
        document.getElementById('app'))*/
    // Vue中message发生改变时页面会发生改变,react中需要重新渲染DOM
    function btnClick() {
        console.log("按钮发生点击")
        message = "Hello React"
        render()
    }

    function render(){
        ReactDOM.render(
            <div>
                <h2>{message}</h2>
                <button onClick={btnClick}>改变文本</button>
            </div>,
            document.getElementById('app'))
    }

    render()
</script>
</body>
</html>

组件化开发

整个逻辑可以看做是一个整体,那么就可以将它封装成一个组件

ReactDOM.render的第一个参数是一个HTML或者一个组件

所以可以将之前的业务逻辑封装到一个组件中,然后传入到ReactDOM.render函数的第一个参数

封装组件

暂时使用类的方式封装组件

  • 定义一个类(类名大写,组件的名称必须是大写的,小写会被认为是HTML元素),继承自React.Component
  • 实现当前组件的render函数,render中返回的jsx内容就是之后React去渲染的内容
class App extends React.Component {
  render() {
    return(<h2>Hello World</h2>)
  }
}

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

数据依赖

组件中的数据,可以分为两类

  • 参与界面更新的数据:当数据改变时,需要重新将组件渲染的内容
  • 不参与界面更新的数据:当数据改变时,不需要更新将组件渲染的内容

参与界面更新的数据可以称之为参与数据流,这个数据是定义在当前对象的state中

  • 可以通过在构造函数中this.state={定义的数据}
  • 当数据发生变化时,可以调用this.setState来更新数据,并且通知React进行更新操作
  • 在进行更新操作时,会重新调用render函数,并且使用最新的数据来渲染页面
class App extends React.Component {
  constructor() {
    super()
    this.state = {
      message = "Hello World"
    }
  }
}

事件绑定

事件绑定中的this

在类中直接定义一个函数,并且将这个函数绑定到HTML原生的onClick事件上,当前这个函数的this指向的是谁?

class App extends React.Component {

    constructor() {
        super();
        this.state = {
            message : 'Hello world'
        }
    }

    render() {
        return (
            <div>
                <h2>{this.state.message}</h2>
                <button onClick={this.btnClick}>改变文本</button>
            </div>
        )
    }

    btnClick() {
        this.setState({
            message: 'Hello React'
        })
    }
}

默认情况下,btnClick中的this是undefined

  • 在正常的DOM操作中,监听点击,监听函数中的this其实就是节点对象(比如button对象)
  • React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质上是React的Element对象
  • 那么在这里发生监听的时候,react给我们的函数绑定的this,默认情况下就是undefined

我们在绑定的函数中,可能想要使用当前对象,比如执行this.setState函数,就必须拿到当前对象的this

  • 需要在传入函数时,给这个函数直接绑定this
  • 例如 </button onClick={this.btnClick.bind(this)}>改变文本</button>

完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<!-- 添加React依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<div id="app"></div>
<!-- 开始开发 -->
<!-- 注意事项:使用jsx,并且希望script中的jsx的代码被解析,必须在script标签中添加一个属性 text/babel -->
<!-- jsx特点:多个组件最外层(根)只能有一个组件 -->
<script type="text/babel">
    // 封装APP组件
    class App extends React.Component {

        constructor() {
            super();
            this.state = {
                message : 'Hello world'
            }
        }

        render() {
            return (
                <div>
                    <h2>{this.state.message}</h2>
                    <button onClick={this.btnClick.bind(this)}>改变文本</button>
                </div>
            )
        }
    
        btnClick() {
            this.setState({
                message: 'Hello React'
            })
        }
    }
  
    // 渲染组件
    ReactDOM.render(<App/>, document.getElementById('app'))
</script>
</body>
</html>

ES6的class

在ES6之前,通过function来定义类,但是这种模式一直被很多从其他编程转到JavaScript的人所不适应,因为大多数面向对象的语言,都是使用class关键字来定义类的。

JavaScript从ES6开始引入了class关键字,用于定义一个类。

// ES5中如何定义类
 function Person(name,age){
     this.name = name
     this.age = age

 }
 // 定义方法
 // 写在原型上
 Person.prototype.running = function (){
     console.log(this.name,this.age,"running")
 }

 // 通过new创建对象
 // 创建时this绑定到我们新创建的对象上
 var person = new Person("why",18);
 console.log(person.name,person.age)
 person.running(); // 隐式绑定
/ ES6中通过class创建类
class Person {
    // 构造方法
    constructor(name, age) {
        this.name = name
        this.age = age
    }

    // 定义方法
    running() {
        console.log(this)
        console.log(this.name, this.age, "running")
    }
}

const p = new Person("why", 18);
console.log(p.name, p.age)

// this绑定题目
let func = p.running
// 报错,this是undefined
// func();
var obj = {
    name:"kobe",
    age:40
}
func.call(obj)

// 给func重新复制
func = func.bind(obj)
func()

类中有一个constructor构造方法,当我们通过new关键字调用时,就会默认执行这个构造方法

构造方法中可以给当前对象添加属性

类中也可以定义其他方法,这些方法会被放到类的prototypy上

另外,属性也可以直接定义在类上

继承是面向对象的一大特性,可以减少我们重复代码的编写,方便公共内容的抽取。

ES6中增加了extends关键字来作为类的继承

在constructor中,子类必须通过super来调用父类的构造方法,对父类进行初始化,否则会报错。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script>
    /**
     * 面向对象三大特性:封装、继承、多态
     // */
    class Person {
        constructor(name, age) {
            this.name = name
            this.age = age
        }

        running() {
            console.log("running")
        }
    }

    class Student extends Person{
        constructor(name,age,sno) {
            super(name,age);
            this.sno = sno;
        }
    }

    let student = new Student("young",18,110);
    console.log(student.name,student.age,student.sno)
    student.running()

    class Teacher extends Person{
        constructor(name,age,sno) {
            // 子类必须初始化父类对象
            super(name,age);
            this.sno = sno;
        }
    }



   /* class Student {
        constructor(name, age, sno) {
            this.name = name
            this.age = age
            this.sno = sno
        }

        running() {
            console.log("running")
        }
    }

    class Teacher {
        constructor(name, age, title) {
            this.name = name
            this.age = age
            this.title = title
        }

        running() {
            console.log("running")
        }
    }*/

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

数组的map方法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        const names = ["abc","cba","nba","mba"]
        // names.map(回调函数,给前面的回调函数绑定this)
        /**
         * 回调函数有三个参数
         * 1:执行时的对应元素
         * 2:对应的下标值
         * 3. 完整的数组对象
         *
         * map会返回一个新的数组
         */
       const newNames =  names.map((item,index,arr)=>{
           return  item + '000'
        })
        console.log(newNames)
    </script>
</body>
</html>

案例练习

电影列表展示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<!-- 2.编写react语法 -->
<script type="text/babel">
    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                movies: ["大华西游", "盗梦空间", "流浪地球", "星际穿越"]
            }
        }

        render() {
            // 通过声明数组,并且用push的方式
            const liArray = [];
            for (let movie of this.state.movies) {
                liArray.push(<li>{movie}</li>)
            }
            return (
                <div>
                    <h2>电影列表1</h2>
                    <ul>
                        {liArray}
                    </ul>

                    <h2>电影列表2</h2>
                    <ul>
                        {
                            this.state.movies.map((item) => {
                                return <li>{item}</li>
                            })
                        }
                    </ul>
                </div>
            )
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

计数器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                counter: 0
            }
        }

        render() {
            return (
                <div>
                    <h2>当前计数:{this.state.counter}</h2>
                    <button onClick={this.increment.bind(this)}>+1</button>
                    <button onClick={this.decrement.bind(this)}>-1</button>
                </div>
            )
        }

        increment() {
            this.setState({
                counter: this.state.counter + 1
            })
        }

        decrement() {
            this.setState({
                counter: this.state.counter - 1
            })
        }

    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

JSX

const element = <h2>Hello word</h2>
ReactDOM.render(element, document.getElementById("app"))

这段element变量的声明右侧赋值的标签语法

  • 它不是一段字符串,没有使用引号包裹,看起来像一段html
  • 正常情况下这么写是不可以的,如果将type="text/babel"去掉,就会出现语法错误

JSX是什么

  • JSX是一种JavaScript的语法扩展(extension),也在很多地方称之为JavaScript XML
  • 它用于描述我们的UI界面,并且其完全可以和JavaScript融合在一起使用
  • 它不同于Vue中的模块语法,不需要专门学习模块语法中的一些指令(v-if,v-for,v-else,v-bind)

React认为渲染逻辑本质上与其他UI逻辑存在内在耦合

  • 比如UI需要绑定时间(button,a原生标签等)
  • 比如UI中需要展示数据状态,在某些状态发生改变时,又需要改变UI

他们之间是密不可分的,所以react没有将标记分离到不同的文件中,而是组合到到了一起,这个地方就是组件(Component)

JSX其实是嵌入到JavaScript中的一种结构语法。

JSX的书写规范

  • JSX的顶层只能有一个根元素,所以很多时候我们会在外层包裹一个div原生(或者后面学习的Fragment)
  • 为了方便乐队,通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写
  • jsx中的标签可以是单标签,也可以双标签,如果是单标签,必须以/>结尾

jsx中的注释

{/*我是注释*/}

jsx嵌入变量

  • 情况一:当变量是number、string、array类型时,可以直接显示
  • 情况二:当变量是null、undefined、boolean类型时,内容为空
    • 如果希望可以显示null、undefined、boolean,那么需要转换成字符串
    • 可以调用toString()方法,或者直接拼接字符串,或者String(变量)等方式
  • 情况三:对象类型不能作为子元素(not valid as a React child)

jsx嵌入表达式

  • 可以是运算表达式,如数字计算,字符串拼接等
  • 可以是三目表达式
  • 可以是一个函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                // 在{}可以正常显示
                name: "why",
                age: 18,
                names: ["abc", "cba", "nba"],
                // 在{}不能显示(忽略)
                test1: null,
                test2: false,
                test3: undefined,
                flag: true,

                // 对象不能作为jsx的子类
                friend :{
                    name: "kobe",
                    age: 40
                }
            }
        }

        render() {
            return (
                <div>
                    <h2>{this.state.name}</h2>
                    <h2>{this.state.age}</h2>
                    <h2>{this.state.names}</h2>

                    <h2>{this.state.test1}</h2>
                    <h2>{this.state.test2}</h2>
                    <h2>{this.state.test3}</h2>

                    <h2>{this.state.flag && "您好"}</h2>
                    <h2>{this.state.flag ? "您好" : null}</h2>
                    {/* 会报错 */}
                    <h2>{this.state.friend}</h2>
                </div>
            )
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

JSX的使用

JSX绑定属性

  • 比如元素都会有title属性
  • 比如img元素会有src属性
  • 比如a元素会有herf属性
  • 比如元素可能需要绑定class
  • 比如原生使用内联样式style
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<div style="color: red; font-size: 30px;"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">
    function getSizeImage(imgUrl, size) {
        return imgUrl + `?param=${size}x${size}`
    }

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                title: "标题",
                imgUrl: "http://p2.music.126.net/L8IDEWMk_6vyT0asSkPgXw==/109951163990535633.jpg",
                link: "http://www.baidu.com",
                active: true
            }
        }

        render() {
            const {title, imgUrl, link, active} = this.state
            return (
                <div>
                    {/*绑定普通属性*/}
                    <h2 title={title}>我是标题</h2>
                    <img src={getSizeImage(imgUrl, 140)} alt=""/>
                    <a href={link} target="_blank">百度一下</a>

                    {/* 绑定class
                    	  class要写成className
                    */}
                    <div className="box title">我是div元素</div>
                    <div className={"box title " + (active ? "active" : "")}>我也是div元素</div>
                    <label htmlFor=""></label>

                    {/* 绑定style*/}
                    {/* 第一层括号表示使用jsx绑定,第二层表示使用一个对象
                        属性red要用引号引起来,否则会认为是被定义的属性,属性名要使用驼峰命名
                    */}
                    <div style={{color: "red", fontSize: "50px"}}>我是div,绑定style属性</div>

                </div>
            )
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

事件绑定

  • 如果原生DOM原生有一个监听事件,我们如何操作
    • 方式一:获取DOM原生,添加监听时间
    • 方式二:在HTML原生中,直接绑定onClick
  • 在React中如何操作
    • 在React事件的命名采用小驼峰(lowCamelCase),而不是纯小写
    • 需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行

this的绑定问题

  • 事件执行后,我们可坑需要获取当前类的对象中的相关属性,这个时候需要用到this
    • 如果这里直接打印this,会发现它是一个undefined
  • 为什么是undefined
    • 原因是btnClick函数并不是我们主动调用的,而是当button发生改变,React内部调用了btnClick函数
    • 而它内部调用时,并不知道如何绑定正确的this

解决this的问题

  • 方案一:bind给btnClick显示绑定this
  • 方案二:使用ES6的class field语法
  • 方案三:事件监听时传入箭头函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                message: "你好",
                counter: 100
            }

            this.btnClick = this.btnClick.bind(this)
        }

        render() {
            return (
                <div>
                    {/* 方案一:bind绑定this(显示绑定)*/}
                    <button onClick={this.btnClick}>按钮1</button>

                    {/* 方案二:定义函数式,使用箭头函数*/}
                    <button onClick={this.increment}>+1</button>

                    {/* 方案三:直接传入一个箭头函数,在箭头函数中调用需要执行的函数*/}
                    <button onClick={() => {
                        this.decrement("young")
                    }}>-1
                    </button>
                </div>
            )
        }

        btnClick() {
            console.log(this.state.message);
        }

        // 箭头函数中永远不绑定this,会向上查找最近的this
        // ES中给对象增加属性 class field
        increment = () => {
            console.log(this.state.counter);
        }

        decrement(name) {
            console.log(this.state.counter, name)
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

事件参数传递

  • 在执行事件函数时,有可能我们需要获取一些参数信息,比如event对象、其他参数
  • 情况一:获取event对象
    • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
    • 假如我们用不到this,那么直接传入函数就可以获取到event对象
  • 情况二:获取更多参数
    • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                movies: ["大话西游", "钢铁侠", "梦幻西游", "蜘蛛侠"]
            }
            this.btnClick = this.btnClick.bind(this)
        }

        render() {
            return (
                <div>
                    <button onClick={this.btnClick}>按钮</button>
                    <ul>
                        {
                            this.state.movies.map((item, index, arr) => {
                                return (
                                    <li className="item"
                                        onClick={event => {
                                            this.liClick(item, index, event)
                                        }}
                                        title="li">
                                        {item}
                                    </li>
                                )
                            })
                        }
                    </ul>
                </div>
            )
        }

        btnClick(event) {
            console.log("按钮发生了点击", event)
        }

        liClick(item, index, event) {
            console.log("li发生了点击", item, index, event)
        }
    }


    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

条件渲染

某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染部分内容

  • 在vue中,我们可以通过指令来控制,比如v-if,v-show
  • 在react中,所有的条件判断都和普通的JavaScript代码一致

常见的条件渲染的方式

  • 条件判断语句,适合逻辑较多的情况
  • 三目运算符,适合逻辑比较简单的情况
  • 逻辑与运算符&&,适合如果条件成立,渲染一个组件;如果条件不成立,什么内容都不渲染
  • v-show的效果。主要是控制display属性是否为none
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                isLogin: true
            }
        }

        render() {
            const {isLogin} = this.state
            // 通过if判断:逻辑代码非常多的情况
            let welcome = null;
            let btnText = null

            if (isLogin) {
                welcome = <h2>欢迎回来</h2>
                btnText = "退出"
            } else {
                welcome = <h2>请先登录</h2>
                btnText = "登录"
            }

            return (
                <div>
                    <div>我是div元素</div>

                    {welcome}

                    {/* 三目运算符*/}
                    <button onClick={e => {
                        this.loginClick()
                    }}>{isLogin ? "退出" : "登录"}</button>

                    <br/>

                    <h2>{isLogin ? "你好啊" : null}</h2>

                    {/* 逻辑与&&*/}
                    <h2>{isLogin && "你好啊"}</h2>
                    {isLogin && <h2>你好啊</h2>}
                </div>
            )
        }

        loginClick() {
            this.setState({
                isLogin: !this.state.isLogin
            })
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                isLogin: true
            }
        }

        render() {
            const {isLogin} = this.state
            const titleDisplayValue = isLogin ? "block" : "none"
            return (
                <div>
                    <button onClick={e => this.loginClick()}>{isLogin ? "退出" : "登录"}</button>
                    <h2 style={{display: titleDisplayValue}}>你好啊</h2>
                </div>
            )
        }

        loginClick() {
            this.setState({
                isLogin: !this.state.isLogin
            })
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

列表渲染

真实开发中,我们会从服务器请求到大量的数据,数据会以列表的形式存储

  • 比如歌曲、歌手、排行榜列表的数据
  • 比如商品、购物车、评论列表的数据
  • 比如好友消息、动态、联系人列表的数据

在React中并没有Vue模块语法中的v-for指令,而且需要我们通过JavaScript代码的方式组织数据,转成JSX

  • 很多从vue转型到react的同学非常不习惯,认为vue的方式更加简洁明了
  • 但是React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活

在React中,展示列表最多的方式就是使用数组的map高阶函数

很多时候,我们在展示一个数组中的数据之前,需要先对它进行一些处理

  • 比如过滤掉一些内容:filter函数
  • 比如截取数组中的一部分内容:slice函数
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>

<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                names: ["abc", "cbd", "nba", "mba", "dna"],
                numbers: [110, 123, 45, 32, 50, 10, 55, 8, 666]
            }
        }

        render() {
            return (
                <div>
                    <h2>名字列表</h2>
                    <ul>
                        {
                            this.state.names.map(item => {
                                return <li>{item}</li>
                            })
                        }
                    </ul>

                    <h2>数字列表(过滤)</h2>
                    <ul>
                        {
                            this.state.numbers.filter(item => item >= 50).map(item => <li>{item}</li>)
                        }
                    </ul>
                    
                    <h2>数字列表(截取)</h2>
                    <ul>
                        {
                            this.state.numbers.slice(0, 4).map(item => <li>{item}</li>)
                        }
                    </ul>
                </div>
            )
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>

JSX的本质

jsx仅仅是React.createElement(component, props, …children)函数的语法糖

所有的jsx最终都会被转换成React.createElement的函数调用

React.createElement在源码的packages–>react–>src–>ReactElement.js文件

createElement需要传递三个参数

  1. type
    • 当前ReactElement的类型
    • 如果是标签元素,那么就用字符串表示,如"div"
    • 如果是组件元素,那么就直接使用组件的名字
  2. config
    • 所有jsx中的属性都在config中以对象的属相和值的形式存储
  3. children
    • 存放在标签中的内容,以children数组的方式进行存储
    • 如果是多个元素,React内部有对它们进行处理
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
    
  <div id="app"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {
          
        }
      }

      render() {
        return (
          <div>
            <div className="header">
              <h1 title="标题">我是标题</h1>
            </div>
            <div className="content">
              <h2>我是页面的内容</h2>
              <button>按钮</button>
              <button>+1</button>
              <a href="http://www.baidu.com">百度一下</a>
            </div>
            <div className="footer">
              <p>我是尾部的内容</p>
            </div>
          </div>
        )
      }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
  </script>


</body>
</html>

Babel

默认jsx是通过babel帮助我们进行语法转换的,所以我们之前写的jsx代码都需要babel进行转换

可以在babel的官网中快速查看转换的过程https://babeljs.io/repl/#?presets=react

直接编写jsx代码

如果我们自己来编写React.createElement代码,就可以不使用babel相关的内容

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="app"></div>

  <script src="../react/react.development.js"></script>
  <script src="../react/react-dom.development.js"></script>
  <script src="../react/babel.min.js"></script>

  <script type="text/babel">
    class App extends React.Component {
      constructor(props) {
        super(props);

        this.state = {

        }
      }

      render() {
        return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("div", {
          className: "header"
        }, /*#__PURE__*/React.createElement("h1", {
          title: "\u6807\u9898"
        }, "\u6211\u662F\u6807\u9898")), /*#__PURE__*/React.createElement("div", {
          className: "content"
        }, /*#__PURE__*/React.createElement("h2", null, "\u6211\u662F\u9875\u9762\u7684\u5185\u5BB9"), /*#__PURE__*/React.createElement("button", null, "\u6309\u94AE"), /*#__PURE__*/React.createElement("button", null, "+1"), /*#__PURE__*/React.createElement("a", {
          href: "http://www.baidu.com"
        }, "\u767E\u5EA6\u4E00\u4E0B")), /*#__PURE__*/React.createElement("div", {
          className: "footer"
        }, /*#__PURE__*/React.createElement("p", null, "\u6211\u662F\u5C3E\u90E8\u7684\u5185\u5BB9")));
      }
    }

    ReactDOM.render(<App />, document.getElementById("app"));
  </script>

</body>
</html>

虚拟DOM

虚拟DOM的创建过程

我们通过React.createElement最终创建出来一个ReactElement对象

return ReactElement(
	type,
  key,
  ref,
  self,
  source,
  ReactCurrentOwner.current,
  props
)

React利用ReactElement对象组成了一个JavaScript对象树

这个JavaScript的对象树就是虚拟DOM(Virtual DOM)

我们可以将之前的jsx返回结果进行打印,就可以查看到ReactElement对象树

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>

<div id="app"></div>

<script src="../react/react.development.js"></script>
<script src="../react/react-dom.development.js"></script>
<script src="../react/babel.min.js"></script>

<script type="text/babel">
    class App extends React.Component {
        constructor(props) {
            super(props);

            this.state = {}
        }

        render() {
            // jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 真实DOM
            // jsx -> createElement函数 -> ReactElement(对象树) -> ReactDOM.render -> 原生的控件(UIButton/Button)
            var elementObj = (
                <div>
                    <div className="header">
                        <h1 title="标题">我是标题</h1>
                    </div>
                    <div className="content">
                        <h2>我是页面的内容</h2>
                        <button>按钮</button>
                        <button>+1</button>
                        <a href="http://www.baidu.com">百度一下</a>
                    </div>
                    <div className="footer">
                        <p>我是尾部的内容</p>
                    </div>
                </div>
            )
            console.log(elementObj);
            return elementObj;
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>


</body>

</html>

JSX代码

虚拟DOM-jsx

ReactElement对象
虚拟DOM-ReactElement对象

真实DOM
虚拟DOM-真实DOM

为什么使用虚拟DOM

为什么采用虚拟DOM,而不是直接修改真实DOM

  • 很难追踪状态发生的变化:原有的开发模式,很难追踪到状态发生的变化,不方便针对我们应用程序进行调试
  • 操作真实DOM性能较低:传统的开发模式,会频繁的对DOM进行操作,而这种做法的性能非常的低

DOM操作性能低:

  • document.createElement本身创建出来的是一个非常复杂的对象https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
  • DOM操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的DOM操作

频繁操作DOM的问题

比如现在有一组数组需要渲染:[0,1,2,3,4],我们可以通过ul和li将他们展示出来

<ul>
  <li>0</li>
  <li>1</li>
  <li>2</li>
  <li>3</li>
  <li>4</li>
</ul>

后来我们又增加了5条数据:[0,1,2,3,4,5,6,7,8,9]

方式一:重新遍历整个数组

方式二:在ul后面追加另外5个li

for (var i=5; i<10; i++){
  var li = document.createElement("li")
  li.innerHTML = arr[i]
  ul.appendChild(li)
}

这段代码的性能非常低效

因为我们通过document.createElement创建元素,再通过ul.appendChild(li)渲染到DOM上,进行了多次DOM操作

对于批量操作,最好的办法不是一次次修改DOM,而是对批量的操作机械能合并,比如可以通过DocumentFragment进行合并

我们可以通过Virtual DOM来帮助我们解决上面的问题

声明式编程

虚拟DOM帮助我们从命令式编程转到了声明式编程的模式

React官方的说法:Virtual DOM是一种编程理念

  • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象
  • 我们可以通过ReactDOM.render让虚拟DOM和真实DOM同步起来,这个过程叫做协调(Reconciliation)

这种编程方式赋予了React声明式的API

  • 你只需要告诉React希望UI是什么状态
  • React来确保DOM和这些状态是匹配的
  • 你不需要直接进行DOM操作,可以从手动更改DOM、属性操作、事件处理中解放出来

案例练习

需求

  1. 在界面以表格的形式,显示一些数据的数据
  2. 在底部显示书籍的总加个
  3. 点击+或者-可以减少书籍数量,如果数量为1,则不能继续-
  4. 点击移除按钮,可以将数据移除,当所有书籍都被移除完毕时,显示:购物车为空

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>
        table {
            border: 1px solid #eee;
            border-collapse: collapse;
        }

        th, td {
            border: 1px solid #eee;
            padding: 10px 16px;
            text-align: center;
        }

        th {
            background-color: #ccc;
        }

        .count {
            margin: 0 5px;
        }
    </style>
    <script src="format-util.js"></script>
</head>
<body>

<div id="app"></div>

<!-- 1.引入依赖 -->
<script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
<script src="./format-util.js"></script>
<script type="text/babel">

    class App extends React.Component {
        constructor(props) {
            super(props);
            this.state = {
                books: [
                    {
                        id: 1,
                        name: '《算法导论》',
                        date: '2006-9',
                        price: 85.00,
                        count: 1
                    },
                    {
                        id: 2,
                        name: '《UNIX编程艺术》',
                        date: '2006-2',
                        price: 59.00,
                        count: 1
                    },
                    {
                        id: 3,
                        name: '《编程珠玑》',
                        date: '2008-10',
                        price: 39.00,
                        count: 1
                    },
                    {
                        id: 4,
                        name: '《代码大全》',
                        date: '2006-3',
                        price: 128.00,
                        count: 1
                    },
                ]
            }
        }

        renderBooks() {
            return (
                <div>
                    <table>
                        <thead>
                        <tr>
                            <th></th>
                            <th>书籍名称</th>
                            <th>出版日期</th>
                            <th>价格</th>
                            <th>购买数量</th>
                            <th>操作</th>
                        </tr>
                        </thead>
                        <tbody>
                        {
                            this.state.books.map((item, index) => {
                                return (
                                    <tr>
                                        <td>{index + 1}</td>
                                        <td>{item.name}</td>
                                        <td>{item.date}</td>
                                        <td>{formatPrice(item.price)}</td>
                                        <td>
                                            <button disabled={item.count <= 1}
                                                    onClick={() => this.changeCount(index, -1)}>-
                                            </button>
                                            <span className="count">{item.count}</span>
                                            <button onClick={() => this.changeCount(index, 1)}>+</button>
                                        </td>
                                        <td>
                                            <button onClick={() => this.remove(index)}>移除</button>
                                        </td>
                                    </tr>
                                )
                            })
                        }
                        </tbody>
                    </table>
                    <span>总价格:{this.getTotalPrice()}</span>
                </div>
            )
        }

        renderEmpty() {
            return (
                <h2>购物车为空</h2>
            )
        }

        render() {
            return this.state.books.length ? this.renderBooks() : this.renderEmpty()
        }


        changeCount(index, num) {
            const newBooks = [...this.state.books]
            newBooks[index].count += num
            this.setState({
                books: newBooks
            })
        }

        remove(index) {
            this.setState({
                books: this.state.books.filter((item, idx) => index != idx)
            })
        }

        getTotalPrice() {
            let totalPrice = this.state.books.reduce((previousValue, item) => {
                return previousValue + item.price * item.count
            }, 0);
            return formatPrice(totalPrice);
        }
    }

    ReactDOM.render(<App/>, document.getElementById("app"));
</script>

</body>
</html>
function formatPrice(price){
    if (typeof price != 'number'){
        price = Number(price) || 0
    }
    return "¥" + price.toFixed(2)
}