react的语法

------------------------------------入门--------------------------------------------------

一.react的概述

1.react是什么

React是一个用于构建用户界面的Javascript库。
用户界面:前端HTML界面。react主要是用来写HTML页面,或构建web应用。
如果从MVC的角度看,react仅仅是视图层(V),也就是负责视图的渲染,而并非提供了完整的M和C的功能。

2.react的特点

  1. 声明式
    只需要描述UI(HTML)看起来是什么样,就像写HTML一样。
    react负责渲染UI,并在数据变化时更新UI

  2. 基于组件

  3. 学习一次,随处使用
    可以开发web,开发移动端原生应用,可以开发VR(虚拟现实)应用

二.安装

1.安装命令

npm i react react-dom

  • react包是核心,提供创建元素,组件等功能
  • react-dom包提供DOM相关功能等

2.使用

1 引入js文件

 <script src="./node_modules/react/umd/react.development.js"></script>
 <script src="./node_modules/react-dom/umd/react-dom.development.js"></script>

2.创建react元素(知道)
三个参数:元素名称,元素属性,元素的子节点(第3个参数及其以后的参数)

const title = React.createElement('h1', null, 'hello react',
            React.createElement('span', null, '我是span'))

3 渲染react元素
两个参数:要渲染的react元素,挂载点

ReactDOM.render(title,document.getElementById('root'))

三.脚手架

1.初始化命令:(推荐)

npx create-react-app my-app

1.其他的初始化命令:
npm init react-app my-app
yarn create react-app my-app
yarn 是Facebook发布的包管理器,可以看做是npm的替代品,功能与npm相同。
2.npx命令的介绍:
以前是先安装脚手架包,再使用这个包中提供的命令
现在是无需安装脚手架包,就可以直接使用这个包提供的命令。
npm极大地提升了我们安装和管理包依赖的体验,在npm的基础之上,npx让npm包中的命令行工具和其他可执行文件在使用上变得更加简单。它极大地简化了我们之前使用纯粹的npm时所需要的大量步骤。

2.启动项目

在项目的根目录执行命令
npm start

3.使用

1)导入react和react-dom两个包
2)调用React.createElement()方法创建react元素
3)调用ReactDOM.render()方法渲染react元素到页面中

index.js

// 1.导入react
import React from 'react'
import ReactDOM from 'react-dom'

// 2.创建react元素
const title=React.createElement('h1',null,'我是新来的!')

//3.渲染react元素
ReactDOM.render(title,document.getElementById('root'))

四.JSX

JSX的特点:
1)JSX是react的核心内容
2)JSX表示在JS代码中写HTML结构,是React声明式的体现
3)使用JSX配合嵌入式的JS表达式、条件渲染、列表渲染,可以描述任意UI结构
4)推荐使用className的方式给JSX添加样式
5)React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能

1 JSX的基本使用

1)createElement()的问题

  • 繁琐不简洁
  • 不直观,无法一眼看出所描述的结构
  • 不优雅,用户体验不爽

而JSX用来代替,并且解决这些问题
在这里插入图片描述

2)JSX简介

JSX是Javascript XML的简写,表示在Javascript代码中写XML(HTML)格式的代码
优势:声明式语法更加直观,与HTML结构相同,降低了学习成本,提高开发效率
JSX是React的核心内容

为什么脚手架中可以使用JSX语法?
1.JSX不是标准的ECMAScript语法,它是ECMAScript的语法扩展
2.需要使用babel编译处理后,才能在浏览器环境中使用
3.create-react-app 脚手架中已经默认有该配置,无需手动配置
4.编译JSX语法的包为:@babel/preset-react

3)JSX使用步骤

1)使用JSX语法创建react元素

const title=<h1>hello JSX</h1>

2)使用ReactDOM.render()方法渲染react元素到页面中

ReactDOM.render(title,document.getElementById('root'))

4)JSX的注意点

1)React元素的属性名使用驼峰命名
2)特殊属性名:class - >calssNamefor - >htmlFortabindex - > tabIndex

const title=<h1 className="title">hello JSX  </h1>

在这里插入图片描述

3)没有子节点的React元素可以使用 />结束,单标签的形式
下面的demo就是span里面没有子节点

const title=<h1>hello JSX <span/> </h1>

4)推荐:使用小括号包裹JSX,从而避免JS中的自动插入分号陷阱

const title=(
  <h1 className="title">hello JSX1234  </h1>
)

2 JSX中使用Javascript表达式

嵌入式JS表达式,数据存储在JS中

语法:{JavaScript表达式}
注意:语法中是单大括号,不是双大括号

const name='Jack'
const title=(
 <h1 className="title">hello JSX ,{name} </h1>
)

ReactDOM.render(title,document.getElementById('root'))

3 JSX的条件渲染

条件渲染:根据条件渲染特定的JSX结构
可以使用 if/else三元运算符逻辑与运算符来实现
if/else

const isLoading=true
const loadData=()=>{
  if(isLoading){
    return <div>loading...</div>
  }
  return <div>数据加载完成,此处显示加载后的数据</div>
}

const title=(
  <h1>条件渲染:
    {loadData()}
  </h1>
)

ReactDOM.render(title,document.getElementById('root'))

三元运算符

const loadData=()=>{
  return isLoading ? (<div>loading...</div>):(<div>数据加载完成,此处显示加载后的数据</div>)
}

逻辑与运算符效果和上面的两种不太一样,但可以实现要么显示,要么隐藏的效果

const loadData=()=>{
  return isLoading &&(<div>loading</div>)
}

4 JSX的列表渲染

  • 使用数组的map()方法
  • 渲染列表的时候,应该添加key属性,key属性的值要保持唯一
  • 原则:map()遍历谁,就给谁添加key属性
  • 尽量避免使用索引号作为key
const song=[
  {id:1,name:'痴心绝对'},
  {id:2,name:'像我这样的人'},
  {id:3,name:'南山南'},
]

const list=(
  <ul>
    {song.map(item=><li key={item.id}>{item.name}</li>)}
  </ul>
)
ReactDOM.render(list,document.getElementById('root'))

5.JSX的样式处理

1)行内样式————style
外面的花括号表示的是JSX语法,里面的花括号表示的是对象

const list=(
  <h1 style={{color:'red',background:'pink'}}>JSX的样式处理</h1>
)

2)类名————className(推荐)

import './css/index.css'

const list=(
  <h1 className="title" style={{color:'red',background:'pink'}}>JSX的样式处理</h1>
)

五.react组件

1.简单介绍

组件是react的一等公民,使用react就是在使用组件
特点:可复用,独立,可组合

2.react组件的两种创建方式

1) 使用函数创建组件

① 特点:

函数组件:使用JS的函数(或箭头函数)创建的组件
特点:

  1. 函数名称必须以大写字母开头,因为react是根据这个区分是组件还是普通的react函数
  2. 函数组件必须有返回值,否则会报错。返回值表示该组件的结构,没值就返回null。

如果返回值为Null,表示不渲染任何内容

② 使用:

  • 用函数名作为组件的标签名
  • 组件标签可以是单标签也可以是双标签
function Hello(){
  return (
    <div>这是我的第一个函数组件</div>
  )
}

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

或者箭头函数的写法:

const Hello=()=> <div>这是我的第er个函数组件</div>

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

2) 使用类创建组件

类组件:使用ES6的class创建的组件

特点:

  1. 类名称也必须以大写字母开头
  2. 类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性
  3. 类组件必须提供render()方法,且该必须有返回值,表示该组件的结构
class Hello extends React.Component{
  render(){
    return (
      <div>这是我的第一个类组件</div>
    )
  }
}

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

3)抽离为独立的js文件

组件作为一个独立的个体,一般都会放到一个单独的js文件中
步骤:
1)创建一个js文件
2)导入React
3)创建组件
4)导出组件
5)在其他js文件中引入上面创建好的的组件,并且渲染
在这里插入图片描述

3.React事件处理

1)事件绑定

  • React事件绑定语法与DOM事件语法相似
  • 语法:on + 事件名称 = {事件处理程序},比如:onClick={()=>{}}
  • 注意:React事件采用驼峰命名法,比如:onMouseEnter,onFocus

类组件的事件处理:

class App extends React.Component{
  handleClik(){
    console.log('我被点了')
  }
  render(){
    return(
      <button onClick={this.handleClik}>点我,点我</button>
    )
  }
}

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

函数组件的事件处理:

function App(){
  function handleClick(){
    console.log('我又被电了')
  }
  return (
    <button onClick={handleClick}>点我</button>
  )
}

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

2)事件对象

可以通过事件处理程序的参数获取到事件对象,React中的事件对象叫做合成事件(对象)
合成事件:兼容所有的浏览器,无需担心跨浏览器兼容问题

class App extends React.Component{
  handleClick(e){
    e.preventDefault();
    console.log('事件对象',e)
  }
  render(){
    return (
      <a href='http://itcast.cn' onClick={this.handleClick}>传智播客</a>
    )
  }
}

4.有状态组件和无状态组件

状态(state)就是数据

  • 函数组件——无状态组件
    函数组件没有自己的状态,只负责静态数据的展示

  • 类组件——有状态组件
    类组件有自己的状态,负责更新UI,让页面动起来

5.组件中的state和setState

1)state的基本使用

  • 状态即数据,是组件内部的私有数据,只能在组件内部使用
  • state的值是对象,表示一个组件中可以有多个数据

在这里插入图片描述

  • 获取state的值 this.state
class App extends React.Component{
  //第一种写法:
  // constructor(){
  //   super()
  //   this.state={
  //     count:0
  //   }
  // }
  //另外一种写法:
  state={
    count:20
  }
  render(){
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
      </div>
    )
  }
}

2)setState修改状态

  • 语法:this.setState({要修改的数据})
  • 注意:不要直接修改state中的值,这是错误的!!!
  • 作用:1.修改state,2.更新UI
  • 思想:数据驱动视图
    在这里插入图片描述
    按钮控制数字的变化:
class App extends React.Component{
  state={
    count:0
  }
  render(){
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={()=>{
          this.setState({
            count:this.state.count + 1
          })
        }}>+1</button>
      </div>
    )
  }
}

3)从JSX中抽离事件处理程序

将逻辑抽离到单独的方法中,保证JSX结构的清晰

class App extends React.Component{
  state={
    count:0
  }
  onIncrement(){
    this.setState({
    //这里的this为undefined
      count:this.state.count + 1
    })
  }
  render(){
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.onIncrement}>+1</button>
      </div>
    )
  }
}

报错:事件处理程序中this的值为undefined
在这里插入图片描述
解决:如何将this指向组件实例(render方法中的this即为组件实例)

4)事件绑定this指向

在这里插入图片描述

①箭头函数

利用箭头函数自身不绑定this的特点

class App extends React.Component{
  state={
    count:0
  }
  onIncrement(){
    this.setState({
      count:this.state.count + 1
    })
  }
  render(){
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={()=>this.onIncrement()}>+1</button>
      </div>
    )
  }
}

②Function.prototype.bind()

利用ES5的bind方法,将事件处理程序中的This与组件实例绑定到一起

class App extends React.Component{
  state={
    count:0
  }
  constructor(){
    super()
    //this绑定到onIncrement方法,绑定一次,后面就不用管了
    this.onIncrement=this.onIncrement.bind(this)
  }
  onIncrement(){
    this.setState({
      count:this.state.count + 1
    })
  }
  render(){
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.onIncrement}>+1</button>
      </div>
    )
  }
}

③class实例的方法

利用箭头函数形式的class实例方法
注意:该语法是实验性的语法,但是由于babel的存在可以直接使用

class App extends React.Component{
  state={
    count:0
  }
  onIncrement=()=>{
    this.setState({
      count:this.state.count + 1
    })
  }
  render(){
    return (
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.onIncrement}>+1</button>
      </div>
    )
  }
}

6.表单处理

1)受控组件

①概念:

其值受到react控制的表单元素。

②使用场景:

  • HTML中的表单元素是可输入的,也就是有自己的可变状态
  • 而React中可变状态通常保存在state中,并且只能通过setState()方法来接收

也就是说 html可维护自己的可变状态,而react有希望所有的可变状态在state中,导致不一致。
所以React将state于表单元素值value绑定到一起,由state的值来控制表单元素的值

③步骤:

1.在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
2.给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)

demo:数据实时变化响应:

class App extends React.Component{
  state={
    txt:''
  }
  handleChange=(e)=>{
    this.setState({
      txt:e.target.value
    })
  }
  render(){
    return (
      <div>
        <div>{this.state.txt}</div>
        <input type="text" value={this.state.txt} onChange={this.handleChange}></input>
      </div>
    )
  }
}

输入框,文本框,下拉框 操作value属性
复选框 操作checkbox属性

④多表单元素优化

每个表单元素都有一个单独的事件处理程序太繁琐,那么如何使用一个事件处理程序 同时处理多个表单元素?

给表单元素添加name属性,名称与state相同,目的:
a.根据表单元素类型获取对应值
b.在change事件处理程序中通过[name]来修改对应的state

class App extends React.Component{
  state={
    txt:'',
    content:'',
    city:'',
    isChecked:true
  }
  handleChange=(e)=>{
    //获取当前DOM对象
    const target = e.target
    
    //根据类型获取值
    const value = target.type === 'checkbox'?target.checked:target.value

    //获取name
    const name = target.name

    this.setState({
      [name]:value
    })
  }
  render(){
    return (
      <div>
        {/* 文本框 */}
        <input name="txt" type="text" value={this.state.txt} onChange={this.handleChange}/> -   <span>{this.state.txt}</span>
        <br/>

        {/* 富文本框 */}
        <textarea name="content" type="text" value={this.state.content} onChange={this.handleChange}></textarea>  -   <span>{this.state.content}</span>
        <br/>        
        
        {/* 下拉框 */}
        <select name="city" value={this.state.city} onChange={this.handleChange}>
          <option value="shanghai">上海</option>
          <option value="beijin">北京</option>
          <option value="guangzhou">广州</option>
        </select>
        -   <span>{this.state.city}</span>
        <br/>

        {/* 复选框 */}
        <input  type="checkbox" name="isChecked" checked={this.state.isChecked} onChange={this.handleChange}/>  -   <span>{this.state.isChecked}</span>
      </div>
    )
  }
}

2)非受控组件

①作用

  • 借助于ref,使用原生DOM方式来获取表单元素值。
  • ref的作用:获取DOM或组件

②使用步骤
在这里插入图片描述
获取input输入框的值:demo

class App extends React.Component{
  constructor(){
    super()
    //创建ref
    this.txtRef=React.createRef()
  }
  //获取文本框的值
  getTxt=(e)=>{
      console.log(this.txtRef.current.value)
  }
  render(){
    return (
      <div>
        <input type='text' ref={this.txtRef}/>
        <button onClick={this.getTxt}>获取文本框的值</button>
      </div>
    )
  }
}

------------------------------------进阶--------------------------------------------------
在这里插入图片描述

六.组件通讯

1.组件的props

作用:接受攒底给组件的数据
如何传:给组件标签添加属性
如何接受:函数组件通过参数props接收数据,类组件通过this.props接收数据
在这里插入图片描述

函数组件:

const Hello = props=>{
  console.log(props)
  return(
    <div>
      <h1>props:{props.name}---{props.age}</h1>
    </div>
  )
}


ReactDOM.render(<Hello name='jack' age={19} />,document.getElementById('root'))

类组件

class Hello extends React.Component{
  render(){
    return(
      <div>
        <h1>props:{this.props.name}-{this.props.age}</h1>
      </div>
    )
  }
}

//如果穿的是费字符串的数据,就用花括号包裹下。这里就是传数字18,如果用字符串传递就是字符串18
ReactDOM.render(<Hello name='rose' age={18} />,document.getElementById('root'))

2.props的特点

1)可以传递任意数据类型

const Hello = props=>{
  console.log('props',props)
  props.fn()
  return(
    <div>
      <h1>props:</h1>{props.tag}
    </div>
  )
  
}

ReactDOM.render(<Hello
  name='rose' 
  age={18} 
  colors={['red','green','blue']}
  fn={()=>console.log('这是一个函数')}
  tag={<p>这是一个标签</p>}
  />, 
document.getElementById('root'))

2)props是只读的对象

只能读取属性的值,无法修改对象

3)注意:

使用类组件时,如果写了构造函数,应该将props传递给super,否则,无法在构造函数中获取到props

class Hello extends React.Component{
  constructor(props){
    super(props)
    console.log(props)
  }
  render(){
    return(
      <div>
        <h1>props:{this.props.name}-{this.props.age}</h1>
      </div>
    )
  }
}


ReactDOM.render(<Hello name='rose' age={18} />,document.getElementById('root'))

3.组件通讯的三种方式

1)父组件 - > 子组件

1.父组件提供要传递的state数据
2.给子组件标签添加属性,值为state中的数据
3.子组件中通过props接受父组件中传递的数据

class Parent extends React.Component{
  state={
    lastName:'王'
  }
  render(){
    return(
      <div className='parent'>
       父组件
       <Child name={this.state.lastName}></Child>
      </div>
    )
  }
}
const Child=(props)=>{
  return (
    <div>
      <p>子组件,接受父组件的数据:{props.name}</p>
    </div>
  )
}

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

2)子组件 -> 父组件

1.父组件提供一个回调函数(勇于接受数据)
2.将该函数作为属性的值,传递个子组件
3.子组件通过props调用回调函数
4.将子组件的数据作为参数传递给回调函数

class Parent extends React.Component{
  state={
    lastName:'王'
  }
  getChildMsg=data=>{
    console.log('接受了子组件传递过来的数据:',data)
  }
  render(){
    return(
      <div className='parent'>
       父组件
       <Child getMsg={this.getChildMsg}></Child>
      </div>
    )
  }
}
class Child extends React.Component{
  state={
    msg:'刷痘印'
  }
  handleClick=()=>{
    this.props.getMsg(this.state.msg)
  }
  render(){
    return (
      <div>
        <p>子组件:</p>
         <button onClick={this.handleClick}>点我,给父组件传递数据</button>
      </div>
    )
  }
}

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

3)兄弟组件

思想状态:状态提升
将共享状态提升到最近的公共组件中,有公共父组件管理这个状态
公共父组件职责:1.提供共享状态;2.提供操作共享状态的方法
要通讯的子组件只需要通过props接受状态或操作状态的方法

class Counter extends React.Component{
  state={
    count:0
  }
  onIncrement= ()=>{
    this.setState({
      count:this.state.count+1
    })
  }
  render(){
    return(
      <div>
        <Child1 count={this.state.count}/>
        <Child2 onIncrement={this.onIncrement}/>
      </div>
    )
  }
}
const Child1 = (props)=>{
  return <h1>计数器:{props.count}</h1>
}
const Child2 = (props)=>{
  return <button onClick={()=>{props.onIncrement()}}>+1</button>
}
ReactDOM.render(<Counter />,document.getElementById('root'))

4.context

1)使用场景

跨组件传递数据

2)使用步骤:

1.调用React.createContext()创建Provider提供数据和Consumer(消费数据)两个组件
2.使用Provider组件作为父节点
3.设置value属性,表示要传递的数据
4.调用Consumer组件接收数据

//创建context得到得两个组件
const {Provider,Consumer}=React.createContext()

class App extends React.Component{
  render(){
    return(
      <Provider value='pink'>
        <div className='app'>
          <Node />
        </div>
      </Provider>
    )
  }
}
const Node=props=>{
  return(
    <div className='node'>
      <SubNode></SubNode>
    </div>
  )
}
const SubNode=props=>{
  return(
    <div className='subNode'>
      <Child></Child>
    </div>
  )
}
const Child=props=>{
  return <div className='child'>
  <Consumer>
    {
      data=><span>我是子节点 -- {data}</span>
    }
  </Consumer>
  </div>
}
ReactDOM.render(<App />,document.getElementById('root'))

5.props的深入

1)children属性

表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
children属性与普通的props一样,值可以是任意值(文本,React元素,组件,甚至是函数)

const App = props=>{
  console.log(props)
  return(
    <div>
      <h1>组件标签的子节点:</h1>
      {props.children}
    </div>
  )
}
ReactDOM.render(<App>我是子节点</App>,document.getElementById('root'))

2)props校验

Ⅰ.出现原因:

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,肯会导致组件内部报错
关键问题:组件的使用者不知道明确的错误原因

Ⅱ.使用场景:

允许在创建组件的时候,就指定props的类型,格式等

Ⅲ.作用:

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
如果传入的数据格式不对,肯会导致组件内部报错
捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

在这里插入图片描述

Ⅳ.使用步骤:

1.安装prop-types
npm i prop-types

2.导入prop-types

import PropTypes from 'prop-types'

3.使用组件名.propTypes={}来给组件的props添加校验规则

App.propTypes={
  colors:PropTypes.array
}

注意:propTypes的大小写问题

4.校验规则通过PropTypes对象来指定

import PropTypes from 'prop-types'
const App=props=>{
  const arr=props.colors
  const lis=arr.map((item,index)=><li key={index}>{item.name}</li>)
  return <ul>{lis}</ul>
}

App.propTypes={
  colors:PropTypes.array
}

ReactDOM.render(<App colors={19}></App>,document.getElementById('root'))

如果类型传错了,那么控制台会直接提示,是类型错误导致报错的。
在这里插入图片描述

Ⅴ.约束规则

1.常见类型:arrayboolfuncnumberobjectstring
2.React元素类型:element
3.必填项:isRequired
4.特定结构的对象:shape({})
在这里插入图片描述
更多类型查询见官网:官网

3)默认值

场景:分页组件 -> 每页显示条数
作用:给props设置默认值,在未传入props的时候生效。如果传入了值,就以传入的值为主。

const App=props=>{
  return <div>
    <h1>此处展示props的默认值:{props.pageSize}</h1>
  </div>
}


App.defaultProps={
  pageSize:10
}

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

七.组件的生命周期

1.概述

1)是什么?组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程
2)意义:组件的生命周期有助于理解组件的运行方式,完成更复杂的组件功能,分析组件错误原因等
3)注意:

  • 生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
  • 钩子函数的作用:未开发人员在不同阶段 操作组件 提供了时机
  • 只有类组件才有生命周期
    在这里插入图片描述

2.生命周期三个阶段

1)创建时(挂载阶段)

  • 执行时机:组件创建时(页面加载时)
  • 三个钩子函数:constructor() -> render() -> componentDidMount()
钩子函数触发时机作用
constructor创建组件时,最先执行1.初始化state; 2.为事件处理程序绑定this
render每次组件渲染都会触发渲染UI(注意:不能调用setState(),否则会死循环)
componentDidMount`组件挂载(wanchengDOM渲染)后1.发送ajax请求(首次进入页面调用请求就是在这);2.可以进行DOM操作
class App extends React.Component{
  constructor(){
    super()
    console.warn('生命周期周期钩子函数:constructor')
  }
  componentDidMount(){
    //进行DOM操作,此外还可以在这里发送ajax请求,获取远程数据
    const title = document.getElementById('title')
    console.log(title)
    console.warn('生命周期周期钩子函数:componentDidMount')

  }
  render(){
    //错误演示!!!不能在render中调用setState()
    // this.setState({
    //   count:1
    // })
    console.warn('生命周期周期钩子函数:render')

    return(
      <div>
        <h1 id='title'>统计痘痘被打败的次数</h1>
        <button id='btn'>打豆豆</button>
      </div>
    )
  }
}

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

2)更新时

①执行时机

  1. 调用setState()触发更新
this.setState({
      count:this.state.count+1
    })
  1. 当组件接收到新的props

  2. 强制更新

this.forceUpdate()

示例:

class App extends React.Component{
  constructor(){
    super()
    this.state={
      count:0
    }
  }
  handleClick=()=>{
    //1.调用setState触发更新
    this.setState({
      count:this.state.count+1
    })

    //强制更新
    this.forceUpdate()
  }
  render(){
    return(
      <div>
        <Counter count={this.state.count}></Counter>
        <button onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
}
class Counter extends React.Component{
  //2.当组件接收到新的属性的时候,也会导致组件重新渲染。也就是说更新属性子组件就会重新渲染
  render(){
    return <h1 >统计痘痘被打败的次数:{this.props.count}</h1>
    
  }
}

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

②两个钩子函数

render()componentDidUpdate()
在这里插入图片描述
如果在componentDidUpdate中直接调用setState()或者发送ajax请求更新状态,会导致递归更新!!!!
正确做法:需要比较前后的props是否相同,来决定是否重新重新渲染。

class App extends React.Component{
  constructor(){
    super()
    this.state={
      count:0
    }
  }
  handleClick=()=>{
    this.setState({
      count:this.state.count+1
    })
  }
  render(){
    return(
      <div>
        <Counter count={this.state.count}></Counter>
        <button onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
}
class Counter extends React.Component{
  render(){
    return <h1 >统计痘痘被打败的次数:{this.props.count}</h1>
  }
  componentDidUpdate(prevProps){
    //componentDidUpdate进行数据的修改需要在if判断中,也就是判断props是否变化来决定是否更新
    if(prevProps.count!==this.props.count){
      this.setState({})
    }
  }
}

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

3)卸载时

执行时机:组件从页面中消失
在这里插入图片描述

class App extends React.Component{
  constructor(){
    super()
    this.state={
      count:0
    }
  }
  handleClick=()=>{
    this.setState({
      count:this.state.count+1
    })
  }
  render(){
    return(
      <div>
        {
          this.state.count > 3?(
            <p>痘痘被打死了!</p>
          ):(
            <Counter count={this.state.count}></Counter>
          )
        }
        <button onClick={this.handleClick}>打豆豆</button>
      </div>
    )
  }
}
class Counter extends React.Component{
  componentDidMount(){
    //开启青石桥
    this.timerId=setInterval(()=>{
      console.warn('定时器开启了')
    },500)
  }
  render(){
    return <h1 >统计痘痘被打败的次数:{this.props.count}</h1>
  }
  componentWillUnmount(){
    console.warn('被清理了')
    //清理定时器
    clearInterval(this.timerId)
  }
}

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

3.不常用钩子函数

在这里插入图片描述
在这里插入图片描述

八.React组件复用

  • 处理方式?服用相似的功能
  • 复用什么?1.state 2.操作state的方法(组件转态逻辑)
  • 两种方式:1.render props模式 2.高阶组件(HOC
  • 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)

1.render props模式

1)思路分析:

  • 思路:将要服用的state和操作state的方法封装到一个组件中

  • 问题1:如何拿到该组件中复用的state?
    在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现)

  • 问题2:如何渲染任意的UI?
    使用该函数的返回值作为要渲染的UI内容(需要组件内部的实现)
    在这里插入图片描述

2)使用步骤

1.创建Mouse组件,在组建中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)
2.将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
3.使用props.render()的返回值作为要渲染的内容

也就是说在子组件通过this.props.render触发调用父组件传过来的render的方法,并且传参数值给父组件。
那么父组件接受子组件传过来的参数,并且调用该render方法。那么子组件只实现了逻辑代码的复用,并没有实现UI结构的复用。父组件掺入的render props负责使用复用的状态来渲染UI结构。

案例:根据鼠标展示坐标和移动端的小花猫。都是调用了Mouse这个封装的组件完成这两个功能的

import img from '../public/images/logo192.png'

class Mouse extends React.Component{
  state={
    x:0,
    y:0
  }
  handleMouseMove=e=>{
    this.setState({
      x:e.clientX,
      y:e.clientY
    })
  }
  componentDidMount(){
    window.addEventListener('mousemove',this.handleMouseMove)
  }
  render(){
    //这里调用的父级的传过来的render的方法,并且传值过去
    return this.props.render(this.state)
  }
}

class App extends React.Component{
  render(){
    return(
      <div>
        <h1>render props模式</h1>
        {/* 根据鼠标展示x,y轴坐标 */}
        <Mouse 
        render={mouse=>{
          return(
            <p>
              鼠标位置:{mouse.x},{mouse.y}
            </p>
          )
        }}
        />
        {/* 移动的猫 */}
        <Mouse
          render={mouse => {
            return (
              <img src={img} 
              alt="猫" 
              style={{ 
                position: 'absolute' ,
                top:mouse.y - 86,
                left:mouse.x-86
              }} />
            )
          }}
        />
      </div>
    )
  }
} 


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

3)children代替render属性

推荐使用children代替render属性
在这里插入图片描述

import img from '../public/images/logo192.png'

class Mouse extends React.Component {
  state = {
    x: 0,
    y: 0
  }
  handleMouseMove = e => {
    this.setState({
      x: e.clientX,
      y: e.clientY
    })
  }
  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove)
  }
  render() {
    //这里调用的父级的传过来的render的方法,并且传值过去
    return this.props.children(this.state)
  }
}

class App extends React.Component {
  render() {
    return (
      <div>
        <h1>render props模式</h1>
        {/* 根据鼠标展示x,y轴坐标 */}
        <Mouse>
          {
            mouse => (
              <p>
                鼠标位置:{mouse.x},{mouse.y}
              </p>
            )
          }
        </Mouse>

        {/* 移动的猫 */}
        <Mouse>
          {
            mouse => (
              <img src={img}
                alt="猫"
                style={{
                  position: 'absolute',
                  top: mouse.y - 86,
                  left: mouse.x - 86
                }} />
            )
          }
        </Mouse>
      </div>
    )
  }
}


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

context用的也是children的方式
在这里插入图片描述

4)代码优化

1.推荐给render.props模式添加props校验

import propTypes from 'prop-types'
//添加props校验
Mouse.propTypes={
  children:propTypes.func.isRequired
}

2.应该在组件卸载时解除mousemove事件绑定

//添加
  componentDidMount() {
    window.addEventListener('mousemove', this.handleMouseMove)
  }
  //解除
  componentWillUnmount(){
    window.removeEventListener('mousemove')
  }

2.高阶组件

1)介绍

目的:实现状态逻辑复用
方式:采用包装(装饰)模式,想手机壳装饰手机一样
高阶组件(HOC,Higher-OrderComponent)是一个函数,接受要包装的组件,返回增强后的组件
高阶组件内部创建一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent

2)使用步骤

1.创建一个函数,名称约定以with开头
2.指定函数参数,参数应该以大写字母开头(作为要渲染的组件)
3.在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回
4.在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件
5.调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中

import img from '../public/images/logo192.png'
//创建高阶组件
function withMouse(WrappedComponent){
  class Mouse extends React.Component{
    state={
      x:0,
      y:0
    }
    handleMouseMove = e =>{
      this.setState(
        {
          x:e.clientX,
          y:e.clientY
        }
      )
    }

    //控制鼠标状态的逻辑
    componentDidMount(){
      window.addEventListener('mousemove',this.handleMouseMove)
    }
    componentWillUnmount(){
      window.removeEventListener('mousemove',this.handleMouseMove)
    }
    render(){
      return <WrappedComponent {...this.state}></WrappedComponent>
    }
  }
  return Mouse
}
//猫捉老鼠的组件:
const Cat = props=>(
  <img 
  src={img} 
  alt="猫"
  style={{
    position:'absolute',
    top:props.y - 96,
    left:props.x - 96
  }}
  />
)
  


//用来测试高阶组件
const Position = props=>(
  <p>
    鼠标当前位置:(x:{props.x},y:{props.y})
  </p>
)

//获取增强后的组件:
const MousePosition = withMouse(Position)

//获取增强后的猫捉老鼠组件:
const MouseCat=withMouse(Cat)

class App extends React.Component{
  render(){
    return(
      <div>
        <h1>高阶组件</h1>

        {/* 渲染增强后的组件 */}
        <MousePosition></MousePosition>
        <MouseCat></MouseCat>
      </div>
    )
  }
}



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

3)设置displayName

1.使用高阶组件存在的问题:得到的两个组件名称相同
在这里插入图片描述
2.原因:默认情况下,React使用组件名称作为displayName
在这里插入图片描述
3.解决方式:为高阶组件设置displayName便于调试时区分不同的组件,displayName用预设值调试信息(React Developer Tools信息)
4.设置方式:
在这里插入图片描述
完整示例:

import img from '../public/images/logo192.png'
//创建高阶组件
function withMouse(WrappedComponent){
  class Mouse extends React.Component{
    state={
      x:0,
      y:0
    }
    handleMouseMove = e =>{
      this.setState(
        {
          x:e.clientX,
          y:e.clientY
        }
      )
    }

    //控制鼠标状态的逻辑
    componentDidMount(){
      window.addEventListener('mousemove',this.handleMouseMove)
    }
    componentWillUnmount(){
      window.removeEventListener('mousemove',this.handleMouseMove)
    }
    render(){
      return <WrappedComponent {...this.state}></WrappedComponent>
    }
  }
  //设置displayName
  Mouse.displayName = `withMouse${getDisplayName(WrappedComponent)}`

  return Mouse
}

function getDisplayName(WrappedComponent){
  return WrappedComponent.displayName || WrappedComponent.name || 'Component'
}

//猫捉老鼠的组件:
const Cat = props=>(
  <img 
  src={img} 
  alt="猫"
  style={{
    position:'absolute',
    top:props.y - 96,
    left:props.x - 96
  }}
  />
)
  


//用来测试高阶组件
const Position = props=>(
  <p>
    鼠标当前位置:(x:{props.x},y:{props.y})
  </p>
)

//获取增强后的组件:
const MousePosition = withMouse(Position)

//获取增强后的猫捉老鼠组件:
const MouseCat=withMouse(Cat)

class App extends React.Component{
  render(){
    return(
      <div>
        <h1>高阶组件</h1>

        {/* 渲染增强后的组件 */}
        <MousePosition></MousePosition>
        <MouseCat></MouseCat>
      </div>
    )
  }
}



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

4)传递props

问题:propsss丢失
原因:高阶组件没有往下传递
解决方式:渲染WrappedComponent时,将statethis.props一起传递给组件
传递方式:
在这里插入图片描述

---------------------------------------React原理揭秘--------------------------------------

九.setState()方法的说明

1.异步更新数据

  • setState()是异步更新数据的
  • 注意:使用该语法时,后面的setState()不要依赖于前面的setState()
    一个方法里面多次使用了setState,只会render一次
class App extends React.Component{
  state={
    count:1
  }
  handleClick=()=>{
    //调用多次setState也只render一次
    this.setState({
      count:this.state.count +1 
    })
    console.log(this.state.count) //1
    //有于是异步,所以还没来得及更新,就进行了第二个setState,此时的count还是1
    this.setState({
      count:this.state.count +1   //1+1
    })
    console.log(this.state.count) //1

  }
  render(){
    console.log('render')
    return(
      <div>
        <h1>计数器:{this.state.count}</h1>
        <button onClick={this.handleClick}>+1</button>
      </div>
    )
  }
}

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

2.推荐语法

推荐:使用setState((state,props)=>{})语法
参数:state表示最新的stateprops表示最新的props

handleClick=()=>{
    //state拿到的就是最新值1
   this.setState((state,props)=>{
     console.log(state)//最新值1
    return{
      count:state.count + 1
    }
   })
   console.log('count',this.state.count)  //1

   //state拿到的就是最新值2
   this.setState((state,props)=>{
    console.log(state)//最新值2
    return{
      count:state.count + 1
    }
   })
   console.log('count',this.state.count)  //1
  }

3.第二个参数

场景:在状态更新(页面完成重新渲染)后立即执行某个操作
语法:setState(update,callback)

这个功能和componentDidMount功能类似,可相互替代

handleClick=()=>{
   this.setState((state,props)=>{
    return{
      count:state.count + 1
    }
   },
   //状态更新后并且重新渲染后,立即执行
   ()=>{
     console.log('状态更新完成',this.state.count)//2
     console.log(document.getElementById('title'))
   }
  )
  console.log(this.state.count)//1
  }

十.JSX语法的转换过程

1.JSX仅仅是createElement()方法的语法糖(简化语法)
2.JSX语法被@babel/preset-react插件编译为createElement()方法
3.React元素:是一个对象,用来描述你希望在屏幕上看到的内容
在这里插入图片描述

十一.组件更新机制

  • setState()的两个作用:1.修改state ;2.更新组件(UI)
  • 过程:父组件重新渲染时,也会重新渲染子组件。但只会渲染自己组件和他的所有子组件
    在这里插入图片描述

十二.组件性能优化

1.减轻state

state里面只存储跟组件渲染相关的数据(比如:count、列表数据、loading等)
注意:不用做渲染的数据不要放在state中,比如定时器id等,这种需要在多个方法中用到的数据,应该放在this
在这里插入图片描述

2.避免不必要的重新渲染

组件的更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
存在的问题:子组件如果没有任何变化时,也会重新渲染
解决:使用钩子函数shouldComponentUpdate(nextProps,nextState){},通过返回值决定该组件是否重新渲染,返回true表示重新渲染,false表示不重新渲染。nextProps最新的参数,nextState最新的状态
触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate -> render)

示例1:nextState

通过nextState实现的案例,父组件调用shouldComponentUpdate方法

class App extends React.Component{
  state={
    number:0
  }
  handleClick=()=>{
    this.setState(()=>{
      return{
        number:Math.floor(Math.random()*3)
      }
    })
  }
  shouldComponentUpdate(nextProps,nextState){
    console.log('最新状态:',nextState,',当前状态:',this.state)
    //原始写法
    if(nextState.number === this.state.number){
      return false
    }
    return true
    //简化写法
    // return nextState.number !== this.state.number
  }
  render(){
    console.log('render')
    return(
      <div>
        <h1>随机数:{this.state.number}</h1>
        <button onClick={this.handleClick}>重新生成</button>
      </div>
    )
  }
  
}

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

示例1:nextProps

通过nextProps实现的案例,子组件调用shouldComponentUpdate方法

class App extends React.Component{
  state={
    number:0
  }
  handleClick=()=>{
    this.setState(()=>{
      return{
        number:Math.floor(Math.random()*3)
      }
    })
  }
  render(){
    return(
      <div>
        <NumberBox number={this.state.number}></NumberBox>
        <button onClick={this.handleClick}>重新生成</button>
      </div>
    )
  }
  
}
class NumberBox extends React.Component{
  shouldComponentUpdate(nextProps){
    return nextProps.number !== this.props.number
  }
  render(){
    return <h1>随机数:{this.props.number}</h1>
  }
}

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

3.React组件复用

render-props和高阶组件

4.纯组件

1.纯组件内部的对比是shadow compare(浅层对比)
2.值类型和引用类型

  • 值类型:比较两个值是否相同,不同的话就可以就可以渲染组件
    在这里插入图片描述
  • 引用类型:只比较对象的引用(地址)是否相同
    引用传递导致只要赋值,他俩就指向同一个地址,所以他俩地址相同。导致即使状态发生了变化,但页面的数据不会重新渲染。
    在这里插入图片描述
    引用类型的解决:
    stateprops属性值为引用类型时,应该创建数据,不要直接修改原数据!
    在这里插入图片描述

5.虚拟DOM和Diff算法

React更新树图的思想:只要state变化句重新渲染视图
问题:组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染到页面吗?
回答:不是的。
问题:React是如何做到部分更新的?
回答:虚拟DOM配合Diff算法

1)虚拟DOM:

本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容(UI)。
虚拟DOM ->state+JSX

在这里插入图片描述

2)执行过程

1.初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(树)。
2.根据虚拟DOM生成真正的DOM,渲染到页面中。
3.当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
4.与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的内容。
5.最终,React只将变化的内容更新(path)到DOM中,重新渲染到页面。
在这里插入图片描述

3)虚拟DOM的价值

虚拟DOM的真正价值不仅仅是性能,而是 让React脱离了浏览器环境的束缚。
因为虚拟DOM不是真正的DOM,他只是js的对象,所以只要能运行的js的地方,就能运行react。那么虚拟DOM就可以让React脱离浏览器的环境去运行,这也为React跨平台的运用提供了保障。

十三.React路由

1.使用步骤

注意:以下写法对react-router-dom ^6.0.2版本不使用,暂时没有想到解决的办法。更多参考:详情
1)安装

npm i react-router-dom

2)导入路由的核心组件:

import {BrowserRouter as Router , Route ,Link ,Routes} from 'react-router-dom'

3.Router标签是最外层包裹,
Link标签由to属性指向路由名称,
Routes标签包裹路由内容,
Route`标签指向不同的路由内容,其中path属性指向路径,element指向路由的标签

import {BrowserRouter as Router , Route ,Link } from 'react-router-dom'

const First=()=><p>页面一的内容</p>
const Home=()=><p>这是Home组件的内容</p>

const App=()=>(
  <Router>
    <div>
      <h1>React路由基础</h1>
      <Link to='/first'>页面一</Link>
      <Link to='/home'>首页</Link>
      
      <Route path='/first' component={First} />
      <Route path='/home' component={Home} />
    </div>
  </Router>
)

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

2.路由的执行过程

1.点击Link组件(a标签),修改了浏览器地址栏中的url
2.React路由监听到地址栏url的变化
3.React路由内部遍历所有Route组件,使用路由规则(path)与pathname进行匹配
4.当路由规则(path)能够匹配地址栏中的pathname时,就展示该Route组件的内容
在这里插入图片描述

3.编程式导航

问题:点击登录按钮跳转到后台首页,如何实现?
回答:通过JS代码来实现页面跳转
history是React路由提供的,用于获取浏览器历史记录的相关信息

  • push (path):跳转到某个页面,参数path表示要跳转的路径
  • go(n):前景或后腿到某个页面,参数n表示前进或后腿页面数量
import { BrowserRouter as Router, Route, Link} from 'react-router-dom'


class Login extends React.Component {
  handleLogin=(props)=>{
    //跳转到home页面
    this.props.history.push('/home')
  }
  render() {
    return (
      <div>
        <p>登录页面</p>
        <button onClick={this.handleLogin}>登录</button>
      </div>
    )
  }
  
}
const Home = (props) => {
  const handleClick=()=>{
    //跳转到上一页
    props.history.go(-1)
  }
  return (
    <div>
      <h2>我是后台首页</h2>
      <button onClick={handleClick}>返回登录页面按钮</button>
    </div>
  )

}

const App = () => (
  
  <Router>
    <div>
      <h1>编程式导航</h1>
      <Link to='/login'>去登录页面</Link>
        <Route path='/login' component={Login} ></Route>
        <Route path='/home' component={Home} ></Route>
    </div>
  </Router>
)

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

注意:上面的路由跳转只适合react-router-dom是6以下的版本。6以上版本语法不一样,获取不到dom,暂时没有想到解决的办法。

4.默认路由

表示进入页面时就会自动匹配的路由
默认路由的path为:/

{/* 默认路由 */}
<Route path='/' component={Login} ></Route>

5.模糊匹配

1)模糊匹配模式

问题:当Link组件的to属性值为"/Login"时,为什么默认路由也会被匹配成功?也就是为什么两个路由的内容都显示?
回答:默认情况下,React路由是模糊匹配模式
模糊匹配规则:只要pathnamepath开头就会匹配成功
在这里插入图片描述

2)精确匹配

问题:默认路由任何情况下都展示。如何避免这种问题
回答:给Route组件添加exact属性,让其变成精确匹配模式
精确匹配:只有当pathpathname完全匹配时才会展示该路由(推荐给默认路由添加exact属性)

{/* 此时,该组件只能匹配pathname='/'这一种情况 */}
<Link to='/'>去登录页面</Link>
<Route exact path='/' component={Login} ></Route>

-----------------------------项目实战-------------------------------------------

react项目实战 - 好客租房


版权声明:本文为qq_39928481原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。