React.js 初级入门学习, 你学废了吗?

本文全面介绍了React的基础知识,包括React的起源、特性、组件化、虚拟DOM、生命周期等核心概念,适合初学者快速掌握React的基本用法。

React.js 初级入门学习, 你学废了吗?

React 简介

React 起源于Facebook的内部项目,因为该公司对市场上所有JavaScript MVC框架,都不满意,就决定自己写一套,用来架设Instagram(照片交友)的网站。做出来以后,发现这套东西很好用,就在2013年5月开源了。

由于React的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来Web开发的主流工具。

React学习路线

React 学习路线图

react-developer-roadmap

React与Vue的对比

组件化方面

  1. 什么模块化:是从代码的角度来进行分析的;把一些可复用的代码,抽离为单个的模块;便于项目的维护和开发
  2. 什么是组件化:是从UI界面的角度来进行分析的;把一些可复用的UI元素,抽离为单独的组件;便于项目的维护和开发;
  3. 组件化的好处:随着项目规模的增大,手里的组件越来越多;很方便就能把现有的组件,拼接为一个完整的页面;
  4. Vue是如何实现组件化的:通过.vue文件,来创建对应的组件
  • template 结构
  • script 行为
  • style 样式

在这里插入图片描述

  1. React如何实现组件化:
    React中有组件化的概念,但是并没有像vue这样的组件模板文件;React中,一切都是以JS来表现的

开发团队方面

  • React是由Facebook前端官方团队进行维护和更新的;因此,React的维护开发团队,技术实力比较雄厚;
  • Vue:第一版,主要是有作者尤雨溪专门进行维护的,当Vue更新到2.x版本后,也有了一个以尤雨溪为主导的开源小团队,进行相关的开发和维护;

更多对比

参考:vue还是react?小孩子才做选择题!

为什么要用React

  1. 使用组件化开发方式,符合现代Web开发的趋势
  2. 技术成熟,社区完善,配件齐全,适用于大型Web项目(生态系统健全)
  3. 由Facebook专门的团队维护,技术支持可靠
  4. 使用方式简单,性能非常高,支持服务端渲染

React 特点

  1. 声明式设计 −React采用声明范式,可以轻松描述应用。
  2. 高效 −React通过对DOM的模拟,最大限度地减少与DOM的交互。
  3. 灵活 −React可以与已知的库或框架很好地配合。
  4. JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
  5. 组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
  6. 单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

React中的核心概念

  1. 虚拟DOM(Virtual DOM)
  2. Diff算法(虚拟DOM的加速器,提升React性能的法宝)

虚拟DOM (Virtual DOM)

什么是DOM?

DOM (Document Object Model) 译为文档对象模型,是浏览器中的概念,通过JS对象来表示页面上的元素,并提供了操作DOM对象的APl

HTML DOM 树形结构如下:

在这里插入图片描述

什么是VituralDOM?

用JS对象结构表示 DOM 树的结构

为什么需要VituralDOM?

既然有了DOM 为啥还需要VituralDOM?

为了实现页面DOM元素的高效更新

举例:比如我们要实现对日期的排序。

在这里插入图片描述

  1. 表格中的数据通过请求后台加载到前台浏览器内存中,构成一个对象数组
        {
          date: '2016-05-02',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1518 弄'
        }, {
          date: '2016-05-01',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1517 弄'
        }, {
          date: '2016-05-03',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1519 弄'
        }, {
          date: '2016-05-04',
          name: '王小虎',
          address: '上海市普陀区金沙江路 1516 弄'
        }
  1. 点击按钮之后需要对内存中的对象数组进行排序,然后输出到HTML中

  2. 如何将排序后的数组渲染到页面上?

    • for循环数组,拼接<table>标签,拼接成一个字符串,然后替换掉原先的HTML页面的<table>标签内的内容
    • 使用模板引擎 比如ejs, 使用for标签实现,其本质上也是拼接成字符串,只是模板引擎帮我们完成了这些的工作。
  3. 上面的方式是否存在性能问题?如何才能做到最优的性能?

    • 按需更新,只重新渲染更新的数据所对应的页面元素
  4. 如何做到按需更新?

    • 获取内存中的新旧两棵DOM树,进行对比,得到需要被按需更新的DOM元素:
    • 一个网页渲染的过程:
      1. 浏览器请求服务器获取页面HTML代码
      2. 浏览器要现在内存中,解析DOM结构,并在存览器内存中,渲染出一棵DOM树
      3. 浏览器把DOM树,呈现到页面上
    • 所以我们需要先获取内存的新旧DOM树,比较之后按需更新,最后在把内存中最新的DOM树渲染到页面上。
  5. 如何获取内存中的新旧两棵DOM树?

    • 浏览器没有提供API去获取浏览器内存中的DOM新旧DOM树。
    • 真正的 DOM 元素非常庞大,这是因为标准就是这么设计的。而且操作它们的时候你要小心翼翼,轻微的触碰可能就会导致页面重排,这可是杀死性能的罪魁祸首
      在这里插入图片描述
  6. 如何解决上述的问题,如何比较新旧两棵DOM树?

    • 聪明的程序员想到了可以通过手工模拟的方式去模拟DOM结构–> 虚拟DOM
  7. 如何模拟DOM树结构?

    • 我们知道通过HTML能描述一个DOM树,除了HTML标签的方式还可以使用什么方式去模拟?—> js对象的方式。相对于 DOM 对象,原生的 JavaScript 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以很容易地用 JavaScript 对象表示出来
    • 比如我们需要模拟下面这个div标签的DOM节点元素,要怎么用JS对象去描述?
<div id="myDiv" title="标题">
		 这是我的Div标签
		<p>这是一个p标签</p>
</div>
    • 通过js对象可以进行如下的描述
{
   tagName:"div",
   title:"标题",
	children:[
   		"这是我的Div标签",
      {
      	tagName:"p",
       children:[
        "这是一个p标签"
        ]
      }
   ]
}
  1. 当DOM节点元素发生变化时候,就可以通过虚拟DOM去获取到更新的节点元素。

比如上面的div节点变成了如下:

<div id="myDiv" title="标题111">
		 这是我的Div标签
		<p>这是一个p标签1111</p>
</div>

模拟出来的虚拟DOM表示如下:

{
    "tagName": "div",
    "title": "标题111",
    "children": [
        "这是我的Div标签",
        {
            "tagName": "p",
            "children": [
                "这是一个p标签111"
            ]
        }
    ]
}

通过模拟虚拟DOM就可以很容易知道新旧两个DOM树之间更新的部分在哪里。

在这里插入图片描述

简单总结虚拟DOM(Vitural DOM)的处理方式
  1. 用JS对象结构表示DOM树的结构,然后用这个虚拟DOM树构建一个真正的DOM树,插到文档当中
  2. 当状态变更的时候,重新构造一棵新的虚拟DOM树。然后用新的树和旧的树进行比较,记录两棵树差异
  3. 把第2步骤所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

所以React所有操作都是针对虚拟DOM,然后再把这个虚拟DOM变成浏览器中的DOM元素,最后在渲染到页面上。

简单总结虚拟DOM

用JS对象的形式来模拟页面上DOM嵌套关系(虚拟DOM是以JS对象的形式存在的)这就是React 中虚拟DOM的概念

  • 本质:用JS对象模拟DOM元素和嵌套关系
  • 目的:就是为了实现页面元素的高效更新

Diff算法

通过前面的虚拟DOM我们就可以模拟出浏览器内存中的新旧DOM树,但是要做到高效的更新,还是离不开高效的更新算法,所以我们下面就要提到Diff算法。

当使用React的时候,在某个时间点render() 函数创建了一棵React元素树(虚拟DOM树),
在下一个state或者props更新的时候,render() 函数将创建一棵新的React元素树(虚拟DOM树),
React将对比这两棵树的不同之处,计算出如何高效的更新UI(只更新变化的地方)

  • 只比较同一层级,不跨级比较

  • tag 不相同,则直接删掉重建,不再深度比较
    在这里插入图片描述

  • tag和key(所以一般list都要求要指定key属性),两者都相同,则认为是相同节点,不再深度比较

了解更多

如何实现一个 Virtual DOM 算法

理解 Virtual DOM

React 入门

环境准备

搭建本地开发环境
npx create-react-app my-app
cd my-app
npm start

在这里插入图片描述

参考:https://react.docschina.org/tutorial/tutorial.html#setup-option-2-local-development-environment

在这里插入图片描述

开发者工具

在这里插入图片描述

在这里插入图片描述

Hello React

前面说React 通过读取虚拟DOM(React DOM),然后使用它们来构建 DOM 以及保持随时更新,所以我们第一步就是需要创建虚拟DOM(React DOM)

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

const title = React.createElement("h1", {className: "main"},
    "Hello React!!") //<div id="root"><h1 class="main">Hello React!!</h1></div>
ReactDOM.render(title, document.getElementById('root'))

在这里插入图片描述

嵌套子节点,在<h1>标签外套一层<div>标签:

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


const title = React.createElement("h1", {className: "main"},
    "Hello React!!")
const div = React.createElement("div", {id:"myDiv"},
    "这是个Div",title) //<div id="root"><div id="myDiv">这是个Div<h1 class="main">Hello React!!</h1></div></div>
ReactDOM.render(div, document.getElementById('root'))

createElement()的问题

说明:createElement()方式,代码编写不友好,太复杂

var dv = React.createElement(
  "div",
  { className: "shopping-list" },
  React.createElement(
    "h1",
    null,
    "Shopping List for "
  ),
  React.createElement(
    "ul",
    null,
    React.createElement(
      "li",
      null,
      "Instagram"
    ),
    React.createElement(
      "li",
      null,
      "WhatsApp"
    )
  )
)
// 渲染
ReactDOM.render(dv, document.getElementById('root'))

JSX 的基本使用

  • JSX语法,最终会被编译为createElement()方法
  • 推荐:使用 JSX 的方式创建组件
  • JSX - JavaScript XML
  • JSX的语法需要通过 babel-preset-react 编译后,才能被解析执行

JSX的注意点

  • 如果在 JSX 中给元素添加类, 需要使用 className 代替 class, 因为class是js的关键字
  • 在 JSX 中注释语法:{/* 中间是注释的内容 */}
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

ReactDOM.render(
    <h1 className="main">Hello, React! {/*这是注释*/}</h1>,
    document.getElementById('root')
);

在这里插入图片描述

  • 在 JSX 中可以直接使用 JS代码,直接在 JSX 中通过 {} 中间写 JS代码即可
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

ReactDOM.render(
    <h1 className="main">Hello, React! {alert("Hello World")}</h1>,
    document.getElementById('root')
);

在这里插入图片描述

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

const a1 = true
const a2 = 100
const a3 = "Hello React"
const a4 = new Date()
const element = <h1>Hello, {a3}</h1> //React元素
const user = {firstName:'Kevin',lastName:'Cai'}
function fun(user){
  return user.firstName + ' ' + user.lastName;
}
ReactDOM.render(
    <div>
      <h1>{a1.toString()}</h1>
      <h1>{a2 + 1}</h1>
      <h1>{a3}</h1>
      <h1>{a4.toLocaleString()}</h1>
      <h1>{a1 ? "条件为真" : "条件为假"}</h1>
      {element}
      {fun(user)}
    </div>,
    document.getElementById('root')
)

在这里插入图片描述

  • 在JSX中只有一个根结点

下面这种方式是有问题的

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

ReactDOM.render(
    <h1 className="main">Hello, React!</h1>
    <h1>Hello World!</h1>,
    document.getElementById('root')
);

在这里插入图片描述

下面这种方式才是OK的:

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

ReactDOM.render(
    <div>
      <h1 className="main">Hello, React!</h1>
      <h1>Hello World!</h1>
    </div>,
    document.getElementById('root')
)

在这里插入图片描述

  • 在JSX中节点元素必须闭合,要符合XML规范

下面这种方式在编译的时候就会报错了

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

ReactDOM.render(
    <div>
      <h1 className="main">Hello, React!</h1>
      <hr>
      <h1>Hello World!</h1>
    </div>,
    document.getElementById('root')
)

节点元素必须要闭合

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

ReactDOM.render(
    <div>
      <h1 className="main">Hello, React!</h1>
      <hr/>
      <h1>Hello World!</h1>
    </div>,
    document.getElementById('root')
)

在这里插入图片描述

在这里插入图片描述

React组件

React创建组件的两种方式
  • 通过 JS函数创建(无状态组件)
  • 通过 class创建(有状态组件)
  1. 定义组件最简单的方式就是编写 JavaScript 函数
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Kevin" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

【注意】

  • 函数名称必须为大写字母开头,React通过这个特点来判断是不是一个组件
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

const element = <welcome name="Kevin" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

如果函数名称是小写,React是不会把它当做一个组件,所以页面上是不会显示的。

在这里插入图片描述

  • 函数必须有返回值,返回值可以是:JSX对象或null
  • 返回的JSX,必须有一个根元素
  • 组件的返回值使用()包裹,避免换行问题
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class Welcome extends React.Component {
  render() {
    return 
    <h1>Hello, {this.props.name}</h1>;
  }
}

const element = <Welcome name="Kevin" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

在这里插入图片描述

这个时候可以使用()来包裹,避免换行的问题,建议都加上,避免格式化问题导致出问题。

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

class Welcome extends React.Component {
  render() {
    return(
    <h1>Hello, {this.props.name}</h1>);
  }
}

const element = <Welcome name="Kevin" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

在这里插入图片描述

  1. 同时还可以使用 ES6 的 class 来定义组件:
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
//  react对象继承字React.Component
class Welcome extends React.Component {
// class创建的组件中 必须有rander方法 且显示return一个react对象或者null
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

const element = <Welcome name="Kevin" />;
ReactDOM.render(
    element,
    document.getElementById('root')
);

在这里插入图片描述

上述两个组件在 React 里是等效的。

函数式组件 和 class 组件的使用场景
  1. 如果一个组件仅仅是为了展示数据,那么此时就可以使用 函数组件
  2. 如果一个组件中有一定业务逻辑,需要操作数据,那么就需要使用 class 创建组件,因为,此时需要使用 state
给组件传递数据
  • 组件中有一个 只读的对象 叫做 props,无法给props添加属性
  • 获取方式:函数参数 props
  • 作用:将传递给组件的属性转化为 props 对象中的属性
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'

function Welcome(props) {
  // props ---> { name: 'Kevin', age: 20 }
  return (
      <div>
        <div>Welcome React</div>
        <h3>姓名:{props.name}----年龄是:{props.age}</h3>
      </div>
  )
}

//传递 props:name 和 age(如果你想要传递number类型数据 就需要向下面这样)
const element = <Welcome name="Kevin" age={20}/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

封装组件到独立的文件中

Vue.js是使用.vue文件来声明一个组件,同样React也可以把我们的自定义组件封装到一个独立的文件中。

// 1. 引入React模块
// 由于 JSX 编译后会调用 React.createElement 方法,所以在你的 JSX 代码中必须首先拿到React。
import React from 'react'
import '../index.css'
// 2. 使用function构造函数创建组件
function Welcome(props) {
  return (
      <div>
        <div className="main">Welcome React</div>
        <h3>姓名:{props.name}----年龄是:{props.age}</h3>
      </div>
  )
}
// 3. 导出组件
export default Welcome

使用import Welcome from './component/Welcome' 来导入组件

import React from 'react'
import ReactDOM from 'react-dom'
import Welcome from './component/Welcome'

//传递 props:username 和 age(如果你想要传递number类型数据 就需要向下面这样)
const element = <Welcome name="Kevin" age={20}/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

props和state
props
  • 作用:给组件传递数据,一般用在父子组件之间
  • 说明:React把传递给组件的属性转化为一个对象并交给 props
  • 特点:props是只读的,无法给props添加或修改属性
// props 是一个包含数据的对象参数,不要试图修改 props 参数
// 返回值:react元素
function Welcome(props) {
  // 返回的 react元素中必须只有一个根元素
  return <div>hello, {props.name}</div>
}

class Welcome extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}
state

状态即数据

  • 作用:用来给组件提供组件内部使用的数据
  • 注意:只有通过class创建的组件才具有状态
  • 注意:状态是私有的,完全由组件来控制
import React from 'react'
import ReactDOM from 'react-dom'


class Welcome extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'Kevin'
    }
  }

  render() {
    return <h1>Hello, {this.state.name}</h1>
  }
}

const element = <Welcome/>
ReactDOM.render(
    element,
    document.getElementById('root')
)
组件的生命周期

简单说:一个组件从开始到最后消亡所经历的各种状态,就是一个组件的生命周期

组件生命周期函数的定义:从组件被创建,到组件挂载到页面上运行,再到页面关闭组件被卸载,这三个阶段总是伴随着组件各种各样的事件,那么这些事件,统称为组件的生命周期函数!

组件生命周期函数总览

组件的生命周期包含三个阶段:创建阶段(Mounting)、运行和交互阶段(Updating)、卸载阶段(Unmounting)

组件生命周期 - 创建阶段(Mounting)
  • constructor()
    • 作用:获取props和初始化state
    • 说明:通过 constructor() 的参数props获取
    • 设置state和props
import React from 'react'
import ReactDOM from 'react-dom'


class Welcome extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //设置state和props
      name: props.name
    }
  }

  render() {
    return <h1>Hello, {this.state.name}</h1>
  }
}

const element = <Welcome name="Kevin"/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

  • componentWillMount()
    • 说明:组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
    • 注意:无法获取页面中的DOM对象
    • 注意:可以调用 setState() 方法来改变状态值
    • 用途:发送ajax请求获取数据
import React from 'react'
import ReactDOM from 'react-dom'


class Welcome extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //设置state和props
      name: props.name
    }
  }

  //组件被挂载到页面之前调用,其在render()之前被调用,因此在这方法里同步地设置状态将不会触发重渲染
  componentWillMount() {
    console.warn(document.getElementById('hello')) // null
    this.setState({
      name: "Kevin Cai"
    })
  }

  render() {
    return <h1 id="hello">Hello, {this.state.name}</h1>
  }
}

const element = <Welcome name="Kevin"/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

在这里插入图片描述

  • render()
    • 作用:渲染组件到页面中,无法获取页面中的DOM对象
    • 注意:不要在render方法中调用 setState() 方法,否则会递归渲染
    • 原因说明:状态改变会重新调用render(),render()又重新改变状态
import React from 'react'
import ReactDOM from 'react-dom'


class Welcome extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //设置state和props
      name: props.name
    }
  }
  //渲染组件到页面中,无法获取页面中的DOM对象
  render() {
    console.warn(document.getElementById('hello')) // null
    return <h1 id="hello">Hello, {this.state.name}</h1>
  }
}

const element = <Welcome name="Kevin"/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

在这里插入图片描述

不要再render方法中更新state,状态改变会重新调用render(),render()又重新改变状态,会导致无限递归

import React from 'react'
import ReactDOM from 'react-dom'


class Welcome extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //设置state和props
      name: props.name
    }
  }

  //渲染组件到页面中,无法获取页面中的DOM对象
  render() {
    console.warn(document.getElementById('hello')) // null
    this.setState({
      name: "Kevin Cai"
    })
    return <h1 id="hello">Hello, {this.state.name}</h1>
  }
}

const element = <Welcome name="Kevin"/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

  • componentDidMount()
    • 组件已经挂载到页面中
    • 可以进行DOM操作,比如:获取到组件内部的DOM对象
    • 可以发送请求获取数据
    • 可以通过 setState() 修改状态的值
    • 注意:在这里修改状态会重新渲染
import React from 'react'
import ReactDOM from 'react-dom'


class Welcome extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //设置state和props
      name: props.name
    }
  }

  componentDidMount() {
    // 此时,就可以获取到组件内部的DOM对象
    console.warn(document.getElementById('hello')) 
  }
  render() {
    return <h1 id="hello">Hello, {this.state.name}</h1>
  }
}

const element = <Welcome name="Kevin"/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

组件生命周期 - 运行阶段(Updating)
  • 特点:该阶段的函数执行多次
  • 说明:每当组件的props或者state改变的时候,都会触发运行阶段的函数
  • componentWillReceiveProps()
    • 说明:组件接受到新的props前触发这个方法,props改变
    • 参数:当前组件props值
    • 可以通过 this.props 获取到上一次的值
    • 使用:若你需要响应属性的改变,可以通过对比this.props和nextProps并在该方法中使用this.setState()处理状态改变
    • 注意:修改state不会触发该方法

通过如下的代码来演示props的改变:

import React from 'react'
import ReactDOM from 'react-dom'

class Parent extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //设置state和props
      name: props.name
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState({
      name: "Kevin Cai"
    })
  }

  render() {
    return <button id="hello" onClick={this.handleClick}>
      Parent - Hello {this.state.name}, <Child name={this.state.name}/>
    </button>
  }
}

class Child extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
  }

  componentWillReceiveProps(nextProps) {
    console.log("Child - componentWillReceiveProps")
    console.log(`this.props-->${JSON.stringify(this.props)}`)
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
  }

  render() {
    return <h3>Child - Hello, {this.props.name}</h3>
  }
}

const element = <Parent name="Kevin"/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

在这里插入图片描述

  • shouldComponentUpdate()
    • 作用:根据这个方法的返回值决定是否重新渲染组件,返回true重新渲染,否则不渲染
    • 优势:通过某个条件渲染组件,降低组件渲染频率,提升组件性能
    • 说明:如果返回值为false,那么,后续render()方法不会被调用
    • 注意:这个方法必须返回布尔值!!!
    • 场景:根据随机数决定是否渲染组件,或者比如进度条的更新。
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = {
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState(state => ({
      count: state.count + 1
    }))
  }

  // - 参数:
//   - 第一个参数:最新属性对象
//   - 第二个参数:最新状态对象
  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
    return nextState.count % 2 === 0
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}
    </button>
  }
}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

在这里插入图片描述

  • componentWillUpdate()
    • 作用:组件将要更新
    • 参数:最新的属性和状态对象
    • 注意:不能修改状态 否则会循环渲染
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = {
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState(state => ({
      count: state.count + 1
    }))
  }


  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
    return nextState.count % 2 === 0
  }

  componentWillUpdate(nextProps, nextState) {
    console.log('componentWillUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}
    </button>
  }
}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

在这里插入图片描述

  • render() 渲染
    • 作用:重新渲染组件,与Mounting阶段的render是同一个函数
    • 注意:这个函数能够执行多次,只要组件的属性或状态改变了,这个方法就会重新执行
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = {
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState(state => ({
      count: state.count + 1
    }))
  }


  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
    return nextState.count % 2 === 0
  }

  componentWillUpdate(nextProps, nextState) {
    console.log('componentWillUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
  }

  render() {
    console.log("render.....")
    return <button onClick={this.handleClick}>{this.state.count}
    </button>
  }
}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

  • componentDidUpdate()
    • 作用:组件已经被更新
    • 参数:旧的属性和状态对象
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = {
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState(state => ({
      count: state.count + 1
    }))
  }


  shouldComponentUpdate(nextProps, nextState) {
    console.log('shouldComponentUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
    return nextState.count % 2 === 0
  }

  componentWillUpdate(nextProps, nextState) {
    console.log('componentWillUpdate')
    console.log(`nextProps-->${JSON.stringify(nextProps)}`)
    console.log(`nextState-->${JSON.stringify(nextState)}`)
  }

  render() {
    console.log("render.....")
    return <button onClick={this.handleClick}>{this.state.count}
    </button>
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('componentDidUpdate')
    console.log(`prevProps-->${JSON.stringify(prevProps)}`)
    console.log(`prevState-->${JSON.stringify(prevState)}`)
  }

}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

组件生命周期 - 卸载阶段(Unmounting)
  • 组件销毁阶段:组件卸载期间,函数比较单一,只有一个函数,这个函数也有一个显著的特点:组件一辈子只能执行依次!
  • 使用说明:只要组件不再被渲染到页面中,那么这个方法就会被调用( 渲染到页面中 -> 不再渲染到页面中 )
  • componentWillUnmount()
    • 作用:在卸载组件的时候,执行清理工作,比如
      • 清除定时器
      • 清除componentDidMount创建的DOM对象
了解更多

在这里插入图片描述
上图参考自下方链接的博文

React生命周期的图解

React生命周期图解

生命周期图谱

state和setState
  • 注意:使用 setState() 方法修改状态,状态改变后,React会重新渲染组件
  • 注意:不要直接修改state属性的值,这样不会重新渲染组件!!!
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = {
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.state.count = 1 //这样方式,不会重新渲染组件
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }

}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

  • 使用:
    • 初始化state
    • setState修改state

使用的正确姿势:

  • 修改state的方式一
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //初始化 state
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    // 修改 state 的值
    // 方式一:
    this.setState({
      count: this.state.count + 1
    })
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }

}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)
  • setState() 是异步操作
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //初始化 state
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    // 修改 state 的值
    // 方式一:
    this.setState({
      count: this.state.count + 1
    })
    console.log(this.state.count) //输出当前的this.state.count
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }

}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

可以看到输出的结果并不是最新的,说明setState方法并不是同步的,而是异步的。

那如果我们就想要拿到最新的count的结果呢? 要怎么做?

import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //初始化 state
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    // 修改 state 的值
    // 方式一:
    this.setState({
      count: this.state.count + 1
    },function(){
      // 由于 setState() 是异步操作,所以,如果想立即获取修改后的state
      // 需要在回调函数中获取
      console.log(this.state.count) //输出当前的this.state.count
    })
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }

}

const element = <Counter/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

  • 修改state的方式二
import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //初始化 state
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    // 修改 state 的值
    // 方式二:
    this.setState(function(prevState, props) {
      console.log("prevState:",prevState)
      console.log("props:",props)
      return {
        count: prevState.count + props.increment
      }
    })
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }

}

const element = <Counter increment = {1}/>
ReactDOM.render(
    element,
    document.getElementById('root')
)

在这里插入图片描述

或者还可以写成如下:

import React from 'react'
import ReactDOM from 'react-dom'

class Counter extends React.Component {
  constructor(props) {
    super(props) //通过 constructor() 的参数props获取
    this.state = { //初始化 state
      count: 0
    }
    // 为了在回调中使用 `this`,这个绑定是必不可少的
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    // 修改 state 的值
    // 方式二:
    // 或者 - 注意: => 后面需要带有小括号,因为返回的是一个对象
    this.setState((prevState, props) => ({
      count: prevState.count + props.increment
    }))
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>
  }

}

const element = <Counter increment = {1}/>
ReactDOM.render(
    element,
    document.getElementById('root')
)
组件绑定事件

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

例如,传统的 HTML:

<button onclick="activateLasers()">
  Activate Lasers
</button>

在 React 中略微不同:

<button onClick={activateLasers}>
  Activate Lasers
</button>

在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault 。例如,传统的 HTML 中阻止链接默认打开一个新页面,你可以这样写:

<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>

在 React 中,可能是这样的:

import React from 'react'
import ReactDOM from 'react-dom'

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
      <a href="#" onClick={handleClick}>
        Click me
      </a>
  );
}

const element = <ActionLink/>
ReactDOM.render(
    element,
    document.getElementById('root')
)
事件绑定中的this

必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

import React from 'react'
import ReactDOM from 'react-dom'

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

  handleClick() {
    console.log(this)
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
        <button onClick={this.handleClick}>
          {this.state.isToggleOn ? 'ON' : 'OFF'}
        </button>
    );
  }
}

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

在这里插入图片描述

通过bind绑定
  • 原理:bind能够调用函数,改变函数内部this的指向,并返回一个新函数
  • 说明:bind第一个参数为返回函数中this的指向,后面的参数为传给返回函数的参数
import React from 'react'
import ReactDOM from 'react-dom'

class MyButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {msg : "button"}
  }

  handleClick() {
    console.log(this)
    this.setState({
      msg: '点击事件修改state的值'
    })
  }

  render() {
    return (
        <button onClick={this.handleClick.bind(this)}>
          {this.state.msg}
        </button>
    )
  }
}

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

在这里插入图片描述

import React from 'react'
import ReactDOM from 'react-dom'

class MyButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {msg : "button"}
  }

  handleClick(arg1, arg2) {
    console.log(this)
    this.setState({
      msg: '点击事件修改state的值' + arg1 + arg2
    })
  }

  render() {
    return (
        // 有参数
        <button onClick={this.handleClick.bind(this, 'abc', [1, 2])}>
          {this.state.msg}
        </button>
    )
  }
}

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

在这里插入图片描述

  • 在构造函数中使用bind
import React from 'react'
import ReactDOM from 'react-dom'

class MyButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {msg : "button"}

    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    console.log(this)
    this.setState({
      msg: '点击事件修改state的值'
    })
  }

  render() {
    return (
        <button onClick={this.handleClick}>
          {this.state.msg}
        </button>
    )
  }
}

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

在这里插入图片描述

通过箭头函数绑定
  • 原理:箭头函数中的this由所处的环境决定,自身不绑定this
import React from 'react'
import ReactDOM from 'react-dom'

class MyButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = {msg : "button"}
  }

  handleClick(arg1, arg2) {
    console.log(this)
    this.setState({
      msg: '点击事件修改state的值' + arg1 + arg2
    })
  }

  render() {
    return (
        <button onClick={() => { this.handleClick('参数1', '参数2') }}>
          {this.state.msg}
        </button>
    )
  }
}

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

在这里插入图片描述

条件渲染

React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。

import React from 'react'
import ReactDOM from 'react-dom'

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}


function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}

ReactDOM.render(
    // Try changing to isLoggedIn={true}:
    <Greeting isLoggedIn={false} />,
    document.getElementById('root')
);

在这里插入图片描述

  • 与运算符 &&

通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。

import React from 'react'
import ReactDOM from 'react-dom'

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}


function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  return isLoggedIn && <UserGreeting />;
}

ReactDOM.render(
    // Try changing to isLoggedIn={true}:
    <Greeting isLoggedIn={true} />,
    document.getElementById('root')
);

true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。
因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

在这里插入图片描述

  • 三目运算符

另一种内联条件渲染的方法是使用 JavaScript 中的三目运算符 condition ? true : false。

import React from 'react'
import ReactDOM from 'react-dom'

function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}


function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  //三目运算符
  return isLoggedIn ? <UserGreeting /> : <GuestGreeting />;
}

ReactDOM.render(
    // Try changing to isLoggedIn={true}:
    <Greeting isLoggedIn={false} />,
    document.getElementById('root')
);

在这里插入图片描述

列表 & Key

import React from 'react'
import ReactDOM from 'react-dom'

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
      <li>{number}</li>
  );
  return (
      <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
);

在这里插入图片描述

当我们运行这段代码,将会看到一个警告 Warning: Each child in a list should have a unique “key” prop.,意思是当你创建一个元素时,必须包括一个特殊的 key 属性。

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

import React from 'react'
import ReactDOM from 'react-dom'

function NumberList(props) {
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) =>
      <li key={number.toString()}>
        {number}
      </li>
  );
  return (
      <ul>{listItems}</ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
    <NumberList numbers={numbers} />,
    document.getElementById('root')
);

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用数据中的 id 来作为元素的 key

当元素没有确定 id 的时候,万不得已你可以使用元素索引 index 作为 key

import React from 'react'
import ReactDOM from 'react-dom'

function NumberList(props) {
  const listItems = props.numbers.map((number, index) =>
      <li key={index}>
        {number}
      </li>
  )
  return (
      <ul>{listItems}</ul>
  )
}

const numbers = [1, 2, 3, 4, 5]
ReactDOM.render(
    <NumberList numbers={numbers}/>,
    document.getElementById('root')
)

受控组件

在HTML当中,像input,textarea和select这类表单元素会维持自身状态,并根据用户输入进行更新。
在React中,可变的状态通常保存在组件的state中,并且只能用 setState() 方法进行更新.
React根据初始状态渲染表单组件,接受用户后续输入,改变表单组件内部的状态。
因此,将那些值由React控制的表单元素称为:受控组件。

import React from 'react'
import ReactDOM from 'react-dom'

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
        <form onSubmit={this.handleSubmit}>
          <label>
            名字:
            <input type="text" value={this.state.value} onChange={this.handleChange} />
          </label>
          <input type="submit" value="提交" />
        </form>
    );
  }
}

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

在这里插入图片描述

import React from 'react'
import ReactDOM from 'react-dom'

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: '请撰写一篇关于你喜欢的 DOM 元素的文章.'
    };

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('提交的文章: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
        <form onSubmit={this.handleSubmit}>
          <label>
            文章:
            <textarea value={this.state.value} onChange={this.handleChange} />
          </label>
          <input type="submit" value="提交" />
        </form>
    );
  }
}

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

在这里插入图片描述

import React from 'react'
import ReactDOM from 'react-dom'

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: '葡萄柚'};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('你喜欢的风味是: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
        <form onSubmit={this.handleSubmit}>
          <label>
            选择你喜欢的风味:
            <select value={this.state.value} onChange={this.handleChange}>
              <option value="葡萄柚">葡萄柚</option>
              <option value="酸橙">酸橙</option>
              <option value="椰子">椰子</option>
              <option value="芒果">芒果</option>
            </select>
          </label>
          <input type="submit" value="提交" />
        </form>
    );
  }
}

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

在这里插入图片描述

学习参考

React入门看这篇就够了

React官网文档

React 菜鸟教程

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值