React基础知识

React基本

  • React 中文文档

React 概述

React 是一个用于构建用户界面的 JavaScript 库。

如果从 MVC 的角度来看,React 仅仅是视图层(V),也就是只负责视图的渲染,而并非提供了 完整的 M 和 C 的功能。
React 起源于 Facebook ,并于 2013 年 5 月开源

React 三个特点

1.声明式

  • JSX 语法是声明式的,只需要描述页面长什么样子
  • React.createElement() 是命令式

2.组件化

  • 创建拥有各自状态的组件,再由这些组件构成更加复杂的 UI
  • 组件逻辑使用 JavaScript 编写而非模版

3.一次学习,随处编写

  • 不仅可以开发 web 应用(react-dom),还可以开发原生安卓或ios应用(react-native)

React基本使用

1. 在html定义一个根标签

<div id="root"></div>

2. 引入两个JS文件( 注意引入顺序 )



<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>

3. 创建react元素(类似html元素)

  • 创建元素(不是真正的dom元素,是一个react元素(虚拟dom))

返回值

  • 返回值:React元素
    第一个参数:要创建的React元素名称 字符串
    第二个参数:该React元素的属性 null或者对象 {id: ‘box’}
    第三个及其以后的参数:该React元素的子节点 文本或者其他react元素
// 创建元素(不是真正的dom元素,是一个react元素(虚拟dom))
const h1 = React.createElement("h1",null,"我是react创建出来的h1")
console.log(h1)

虚拟DOM

React基础知识_第1张图片

真实DOM

测试的时候,可以用.上线不能用
// console.dir(root)

React基础知识_第2张图片

4. 渲染 react 元素

  • 将react元素,转成真正dom元素,然后渲染到页面上

  • ReactDOM.render(react元素/react组件, 渲染的位置)

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

React基础知识_第3张图片

特殊属性

第二个参数为属性

<script>
   // 第二个参数为标签的属性
   const h1 = React.createElement("h1", {
       id: "rolls"
   }, "我是react创建出来的h1")

React基础知识_第4张图片

添加样式要使用 className

<script>
// 第二个参数为标签的属性
 const h1 = React.createElement("h1", {
       id: "rolls",
       className:"royce"
 }, "我是react创建出来的h1")

React基础知识_第5张图片

  • 如果写class也能显示,但是控制台会出现错误
    在这里插入图片描述

label的for属性,要改成htmlFor

const label = React.createElement("label",{
	htmlFor:"test"
},"hello react")

JSX

简介

  • JSXJavaScript XML,是React提供的Syntax Sugar(语法糖), 能让我们可以在JS中写html标记语言
  • React.createElement()写起来太复杂了,所以推荐使用更加简洁的jsx
const h1 = 

编译JSX包

注意: 浏览器并不认识jsx 所以需要引入babel将jsx编译成React.createElement的形式

编译 JSX 语法的包为:@babel/preset-react

babel的CDN


<script src="https://unpkg.com/babel-standalone@6/babel.min.js">script>

设置script type


<script type="text/babel">
  const h1 = <h1 className="red">北京 上海 广州</h1>
  ReactDOM.render(h1, document.getElementById("root"))
script>

React基础知识_第6张图片

JSX中插入数据

插值表达式

表达式:简单的理解为:任何有返回值的一段代码

  • 如果要在jsx中使用js的数据,在要插入的地方必须写一个{}(插值表达式)
  • 这个花括号中可以写任何类型的表达式
  • 注意:插值表达式中,可以传入对象和函数,但是不要直接去渲染,如果渲染的时候插值表达式写了对象和函数就会报错,因为react不知道如何将对象和函数渲染到页面上

常见的表达式:字面量\变量\常量\数组\函数\对象\运算\三元

语法

  • JSX中使用JS表达式的语法:{}

注意

  • 注意:{} 里面可以写表达式,但是不能写语句(比如 if , for 等),
  • 注意: {} 如果要写对象,应该是一个react元素或者是行内样式对象

使用

<script type="text/babel">
   // 元素内使用
   const dv = <div className="abc"> JSX中使用表达式: { 1 + 2 } </div>

   // 属性内使用
   const dv = <div title={'我是标题'}>JSX中使用表达式: { 1 + 2 }</div>

   // 直接使用JSX
   const dv = <div>JSX中使用表达式: { <span>JSX自身也是合法的JS表达式</span> }</div>
   
   ReactDOM.render(dv, document.getElementById("root"))
</script>
ReactDOM.render( ["北京","上海","广州"],document.getElementById("root"))

React基础知识_第7张图片

 <script type="text/babel"> 
  /* 
       未捕获的不变违反:对象作为React的子对象无效(找到:键为{name, age}的对象)。如果您打算呈现一组子元素,请使用数组。
   */
   ReactDOM.render( {name:"rolls",age:99},document.getElementById("root"))
</script>

在这里插入图片描述

条件渲染

1. if / else

  • 如果是有条数据不是渲染a就是渲染b的时候,应该使用if或者三元
<script type="text/babel">
    let data
    let odd = 0;
    
    if (odd) {
        data = <h1>莱斯劳斯</h1>
    }else{
        data = <h1>宾利</h1>
    }

    ReactDOM.render(data,document.getElementById("root"))
</script>

2. 三元

<script type="text/babel">
    let odd = 1;
    
    let data = odd > 0? <h1>莱斯劳斯</h1> : <h1>宾利</h1>

    ReactDOM.render(data,document.getElementById("root"))
</script>

3. &&

  • 还有一些情况,要么是渲染,要么不渲染
<script type="text/babel">
    // 如果num=7,展示所有数据 与运算属于短路运算,如果不满足则返回,第一个满足则直接返回第二个
    let num
    let arr = ["莱斯莱斯","宾利","法拉利","兰博基尼","布加迪"]

    const data = <h1>{num =7 && arr}</h1>
    ReactDOM.render(data,document.getElementById("root"))
</script>

列表渲染

map方法

  • react中可以将数组中的元素直接渲染到页面上
  • 因为jsx的原因,可以直接往数组中存储react对象
  • 所以推荐使用数组的 map 方法

key属性

  • 注意:应该给列表项添加 key 属性
<script type="text/babel">
        let data = [{
            name: "劳斯莱斯",
            color: "black",
            engine: "V12"
        }, {
            name: "宾利",
            color: "white",
            engine: "W12"
        }]

        const data_new = data.map((item,index) => {
            return <li key={index}><div>车型:{item.name}</div><div>颜色:{item.color}</div><div>引擎:{item.engine}</div></li>
        })

        let ul = <ul>{data_new}</ul>

        ReactDOM.render(ul,document.getElementById("root"))
</script>

如果没有key属性,也会显示,但是有警告信息

警告:列表中的每个孩子都应该有一个唯一的“键”道具。

React基础知识_第8张图片

样式处理

1. 行内样式

  • 如果样式是数值,可以省略单位
<script type="text/babel">
    let div = <div style={{color:"red",fontSize:50}}>北京 上海 广州</div>
    ReactDOM.render(div,document.getElementById("root"))
</script>

React基础知识_第9张图片

2. 类名(推荐!!!)

let div = <div className="red50">北京 上海 广州</div>
ReactDOM.render(div,document.getElementById("root"))

React基础知识_第10张图片

注意:

  1. React元素的属性名使用小驼峰命名法
  2. 没有子节点的React元素可以写成自闭和标签形式

事件处理

绑定事件

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

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

const div =

// 经测试,无论箭头函数还是function(){}  this都是undefined
<script type="text/babel">
    let btn = <button onClick={()=>{
        console.log(this)
        console.log("北上广不相信眼泪")
    }}>点击我查看控制台</button>
    ReactDOM.render(btn,document.getElementById("root"))
</script>

React基础知识_第11张图片

event.preventDefault()

  • 在react中,阻值默认行为不能使用return false,测试无效
<script type="text/babel">
   let link = <a href="" onClick={(event)=>{
        console.log("a标签被点击")
        event.persist()
        // 阻值默认事件
        event.preventDefault();
   }}>点击我,会提交数据刷新页面</a>
    ReactDOM.render(link,document.getElementById("root"))
</script>
  • 这个事件对象不是DOM中真正的事件对象,是react封装了一层,叫做合成事件,但是还是可以直接去使用这些值(event.clientX)
    console.log(event)
    React基础知识_第12张图片

event.persist()

  • 默认在控制台打印事件对象看不到属性的值,但是可以通过代码获取得到,如果非要在控制台看,调用这个函数就可以
    React基础知识_第13张图片

事件对象

React 中的事件对象叫做:合成事件 (兼容所有浏览器,无需担心跨浏览器兼容性问题)

**注意: **

  1. react中事件处理函数不能使用return false 阻止默认行为.需要使用事件对象的preventDefault()实现
  2. 如果在控制台打印事件对象,属性值都是null.一定要查看的话,调用事件对象.persist()方法
 function handleClick(e) {
     e.preventDefault() //有效
     console.log('事件对象', e) 
     // return false  无效
  }
 const div = (
   <a href='https://www.baidu.com' onClick={handleClick}>
          测试
   </a>
 )
const list = [
  { id: 1, name: 'jack', content: 'rose, you jump i jump' },
  { id: 2, name: 'rose', content: 'jack, you see you, one day day' },
  { id: 3, name: 'tom', content: 'jack,。。。。。' }
]
// 调用map方法,得到一个存储了一堆li标签的数组
const data = list.map((item)=>{
    console.log(item);
    return <li className="box" key={item.id} style={{backgroundColor:"pink"}}><a onClick={(event)=>{
        event.preventDefault();
        console.log(event.target.innerText);
    }}><h3 style={{backgroundColor:"blue"}}>{item.name}</h3><p style={{backgroundColor:"yellow"}}>{item.content}</p></a></li>
})

let ul = <ul>{list.length ? data : <h1>暂无评论</h1>}</ul>
ReactDOM.render(ul,document.getElementById("root"));

React的组件

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素

一个React应用就是由一个个的组件组成的

创建组件的两种方式

函数组件(不能定义状态)

  • 函数组件是没有实例对象的,它this指向undefined

不能定义状态

特点

  1. 组件名首字母必须大写. 因为react以此来区分组件和react元素
  2. 必须有返回值.返回的内容就是组件呈现的结构, 如果返回值为 null,表示不渲染任何内容
  3. 组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签
export default function App(){
    return(
        <div>
            我是一个根组件,所有组件通过我出去
        </div>
    )
}
// 函数名就是组件名
ReactDom.render(<App/>, document.getElementById('root'))
                     

类组件(可以定义状态)

可以定义状态(状态: 组件内部私有的数据)

特点

  1. 组件名首字母必须大写.
  2. 类组件中必须要声明一个render函数
  3. render函数中必须有返回值.
  4. 类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性
  5. 组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签
import React from "react"

export default class Module1 extends React.Component{
    render(){
        return(
            <div>
                <h1>我是最简单的类组件</h1>
            </div>
        )
    }
}
// 如果组件没有子节点,也可以写成自闭和标签
ReactDom.render(<Module1 />, document.getElementById('root'))

示例

import React from "react"

export default class Module2 extends React.Component{
    constructor(){
        // 否则无法使用this
        super()
        // this指向实例对象,state就是状态,固定写法
        // 实例对象就相当于用的时候那个标签
        this.state = {
            count:7,
            msg:"我是一个字符串"
        }
    }

    render(){
        // 在render里面也可通过this找到当前组件实例
        console.log(this);
        return(
            <div>
                <h3>{this.state.count}</h3> <p>{this.state.msg}</p>   
            </div>
        )
    }
} 

React基础知识_第14张图片

组件的状态 state

状态(state)即数据

函数组件没有state,它this指向undefined 一般只负责渲染静态结构

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

无状态组件(函数组件)

  • 函数组件又叫做无状态组件

有状态组件(类组件)

  • 类组件又叫做有状态组件

state的基本使用

  • 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用

  • state 的值是对象,表示一个组件中可以有多个数据

获取状态:this.state

class Hello extends React.Component {   
    constructor() {     
        super()        
        this.state = { count: 0 }  // 初始化state   
    }   
     // 简化语法 
    // state= { count: 0 } 
    
    render() {     
        return ( 
      		<div>{this.state.count}</div>     
        )   
    } 
} 

this.setState({ 要修改的数据 })

注意:不要直接修改 state 中的值,应该使用组件实例的setState方法,修改state的值

//  点击按钮 数字加一
export default class Module3 extends React.Component{
    constructor(){
        // 否则无法使用this
        super()
        // this指向实例对象,state就是状态,固定写法
        // 实例对象就相当于用的时候那个标签
        this.state = {
            count:0,
            msg:"点击按钮数字会增加哦003"
        }
    }

    render(){
        // 在render里面也可通过this找到当前组件实例
        // console.log(this);
        return(
            <div>
                <h3>{this.state.count}</h3> <p>{this.state.msg}</p> 
                <button onClick={()=>{
                    /* 
                        this.state.count++  数据会发生变化,但是视图(页面)不会变化
                        提供了实例对象一个方法 setState()  既可以改变state数据又可以更新视图
                    */
                   this.setState({
                       // 在原来基础上运算的得到新的值再赋值 而不是++ --  性能优化
                       count:this.state.count + 1
                   })
                    console.log(this.state.count);//简单理解为底层异步执行了,所以总是拿上一次的值
                }}>按钮</button>
            </div>
        )
    }
} 
setState() 作用:
  1. 修改 state
  2. 更新UI

setState的注意点

连续调用合并问题

import React, { Component } from "react";

export default class App extends Component {
  state = {
    count: 0,
    msg: "a",
  };

  handleCount = () => {
    // 注意的第一个问题: setState多次连续调用.合并问题
    this.setState({
      count: this.state.count + 1,
      msg: "b",
    });

    this.setState({
      count: this.state.count + 2,
    });

    this.setState({
      count: this.state.count + 3,
    });

    /* 
        合并之后效果其实是这样,并不是覆盖
        this.setState({
            count: this.state.count + 3,
            msg : b
        });
    */
    console.log(this.state.count);
    // 开始是0  延迟一次更新数据,异步
  };
  render() {
    return (
      <div>
        <div>{this.state.count}</div>
        <button onClick={this.handleCount}>点击我加数字</button>
      </div>
    );
  }
}

setState第一个参数传入函数

import React, { Component } from "react";

export default class App extends Component {
state = {
  count: 0,
  msg: "a",
};

handleCount = () => {
  this.setState(
    // 这里接收到的state和props是最新的state和props
    // 第一个参数除了可以像之前一样传入一个对象之外,还可以传入一个函数.如下
    // 这个函数,接收一个最新的state和最新的props
    // 函数要求返回一个对象
    (state, props) => {
      return {
        count: state.count + 1,
      };
    },
    () => {
      console.log(this.state.count);
    }
  );

  this.setState(
    (state, props) => {
      return {
        count: state.count + 2,
      };
    },
    () => {
      console.log(this.state.count);
    }
  );

  this.setState(
    (state, props) => {
      return {
        count: state.count + 3,
      };
    },
    () => {
      console.log(this.state.count);
    }
  );
};
render() {
  return (
    <div>
      <div>{this.state.count}</div>
      <button onClick={this.handleCount}>点击我加数字</button>
    </div>
  );
}
}

// 最终状态中count的结果是 6
  • setState将对组件 state 的更改排入队列,所以调用之后,立刻获取this.state拿到的值很有可能是错误的

    state = {count:0}
    
    this.setState((state, props) => {
        return {
            count: state.count + 1
        }
    })
    this.setState((state, props) => {
        return {
            count: state.count + 2
        }
    })
    this.setState((state, props) => {
        return {
            count: state.count + 3
        }
    })
    // 这里获取到的state中count的值就是错误的
    console.log(this.state.count) // 0 
    

setState的第二个可选参数

import React, { Component } from "react";

export default class App extends Component {
state = {
  count: 0,
  msg: "a",
};

handleCount = () => {
  this.setState({
    count: this.state.count + 1
  },()=>{
  // 第二个参数:是数据修改完毕,并且视图更新完毕,才会调用
  // 如果想要在setState之后立刻得到最新的state. 可以使用第二个可以选参数
      console.log(this.state.count)
  });
};
render() {
  return (
    <div>
      <div>{this.state.count}</div>
      <button onClick={this.handleCount}>点击我加数字</button>
    </div>
  );
}
}

React基础知识_第15张图片

事件绑定this的指向问题

为了提高代码的阅读性,最好把事件处理函数定义在结构的外面.

但是这样就带来了this指向的问题:

handle中的this 指向 undefined (原因: bable编译jsx. 采用的是严格模式, 普通函数this就指向undefined)

class Hello extends React.Component {
    constructor() {
        super() 
        this.state = { count: 0, num: 100 }// 初始化state
    }

    handle() {
        //这里this指向undefined
        this.setState({
            count: this.state.count + 1
        })
    }

    render() {
        return <div onClick={this.handle}>{this.state.count}</div>
    }
}

1.解决this指向 - 箭头函数

//  点击按钮 数字加一
export default class Module4 extends React.Component{
    constructor(){
        // 否则无法使用this
        super()
        // this指向实例对象,state就是状态,固定写法
        // 实例对象就相当于用的时候那个标签
        this.state = {
            count:0,
            msg:"点击按钮数字会增加哦004"
        }
    }
    // 这个函数就是按钮的点击事件处理函数 是加在了原型身上
    //触发点击事件的时候,react底层.最终在原型上找到了这个函数,然后普通调用了这个函数. 函数普通调用this应该执行window. 但是我们的代码,被babel编译过.变成了严格模式,所以this就指向了undefined
    handleCount(){
        // console.log("111"); 直接this.handleCount可以找到它,因为在原型上
        // 要执行代码
        // console.log(this);   //undefined
        this.setState({
            count:this.state.count+1
        })
    }

    render(){
        // 在render里面也可通过this找到当前组件实例
        // console.log(this);
        return(
            <div>
                <h3>{this.state.count}</h3> <p>{this.state.msg}</p> 
                {/*  */}

                {/* 解决方式一 箭头函数 */}
                <button onClick={()=>{
                    // 此时,handleCount就不是事件处理函数了
                    // 箭头函数才是事件处理函数 点击后会执行箭头函数里面代码
                    // 箭头函数没有this 这个this肯定指向当前组件实例
                    this.handleCount()
                }}>按钮</button>
            </div>
        )
    }
} 

2.解决this指向 - bind

export default class Module1 extends React.Component{
    constructor(){
        super()
        this.state = {
            num:1,
            msg:"点击我按钮数字增加",
        }
    // 解决方式2:bind 去原型上找到这个函数绑定给它的实例对象
    this.handleCount = this.handleCount.bind(this)
    }

    // 实例对象上有这个方法
    handleCount(){
        this.setState({
            num : this.state.num +1,
            msg:"数据更新中"
        })
    }
    render(){
        return(   
        <div>
            <h3>{this.state.num}</h3> <p>{this.state.msg}</p>
            <button onClick={ this.handleCount }>按钮</button>
        </div>
        )
    }
}

3.解决this指向 - 类的实例方法

export default class Module1 extends React.Component{
    // constructor(){
    //     super()
    //     this.state = {
    //         num:1,
    //         msg:"点击我按钮数字增加",
    //     }
    // }

    /* 
        ES7草案中提案.如果要给当前类实例添加属性,就不需要写constructor了,应该使用下面语法
    */
    state = {
        num : 1,
        msg :"点击按钮数字增加"
    }
    /*  解决方式3: 类的实例
        这样定义的函数.这个函数直接添加到了当前组件的实例身上
        注意:虽然是ES7草案的语法,但是因为脚手架中使用了babel,所以可以放心使用
    */
    handleCount = () => {
        this.setState({
            num : this.state.num +1,
            msg:"数据更新中"
        })
    }
    render(){
        return(   
        <div>
            <h3>{this.state.num}</h3> <p>{this.state.msg}</p>
            {/* 底层是对象方法调用,所以这个函数里的this,一定指向当前组件实例 */}
            <button onClick={ this.handleCount }>按钮</button>
        </div>
        )
    }
}

组件的props

组件是封闭的,要接收外部数据应该通过 props 来实现

props的作用:接收传递给组件的数据

传递数据:

给组件标签添加属性即可

import React, { Component } from 'react'

import Module1 from "./Module1"

export default class App extends Component {
    state = {
        outdata :"App根组件的数据"
    }

    render() {
        return (
            <div>
                {/* 组件使用组件外部的数据,使用标签属性的方式传递 */}
                <Module1 
                odata={this.state.outdata}
                arr = {[1,3,5,7,6]}
                obj = {{name:"rolls"}}
                />
            </div>
        )
    }
}

接收数据:

函数组件通过参数props接收数据,类组件通过 this.props 接收数据

函数组件

import React from 'react'

export default function Module1(props) {
    console.log(props)
    return (
        <div>
            <h1>{props.odata}</h1>
            <div>{props.arr}</div>
        </div>
    )
}

React基础知识_第16张图片

类组件

import React from "react"

export default class Module1 extends React.Component{
    render(){
        // 使用props属性,来接收组件外部传进来的数据
        console.log(this.props)
        return(
        <div>
            <h3>{this.props.arr}</h3> <p>{this.props.odata}</p>
            <div>{this.props.obj.name}</div>
        </div>
        )
    }
}

React基础知识_第17张图片

props的特点:

  1. 可以给组件传递任意类型的数据,任意哦 即使是一个组件也可以
  2. props 是只读的对象,只能读取属性的值,不要修改props
  3. 注意:使用类组件时,如果写了构造函数,应该将 props 传递给 super(),否则,无法在构造函数中获取到 props
class Hello extends React.Component { 
  constructor(props) { 
    // 推荐将props传递给父类构造函数 
    super(props) 
  } 
  render() { 
    return <div>接收到的数据:{this.props.age}</div> 
  } 
} 

props校验

对于组件来说,props 是外来的,无法保证组件使用者传入什么格式的数据

如果传入的数据格式不对,可能会导致组件内部报错

关键问题:组件的使用者不知道明确的错误原因

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

作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

实现方式:

  1. 导入 prop-types 包
  2. 使用propTypes来给组件的props添加校验规则
//常见类型:array、bool、func、number、object、string还有element(react元素)
// 必填项:isRequired 
// 特定结构的对象:shape({  }) 
import PropTypes from 'prop-types'
class Hello extends React.Component(){
    static propTypes = {
         msg: PropTypes.string.isRequired 
         list: PropTypes.array
         obj: PropTypes.shape({ 
          color: PropTypes.string, 
          fontSize: PropTypes.number  
        }) 
    }
	render(){
        return <div msg="props校验" ></div>
    }
}
  • 另一种
export default class App extends Component {
    state = {
        outdata :"App根组件的数据"
    }

    render() {
        return (
            <div>
                {/* 组件使用组件外部的数据,使用标签属性的方式传递 */}
                <Module1 
                odata={this.state.outdata}
                arr = {[1,3,5,7,6]}
                obj = {{
                    color:"red",
                    fontSize:20
                }}
                str = {"string"}
                num = {1}
                // 刻意写错
                age={""}
                />
            </div>
        )
    }
}

props效验


import React, { Component } from 'react'

// 首先需要引入prop-types
import PropTypes from "prop-types"

export default class Module1 extends Component {
    render() {
        console.log(this.props)
        return (
            <div>
                <h1>
                    {
                        this.props.arr.map((item,index)=>{
                            return <div key={index}>{item}</div>
                        })
                    }
                </h1>
                <div>{this.props.odata}</div>
            </div>
        )
    }
}

// props效验的作用:就是传入props的时候,传入缺失了一些属性,或属性的值类型错误.可以报出更清晰的错误
Module1.propTypes = {
    arr:PropTypes.array.isRequired,
    str:PropTypes.string,
    num:PropTypes.number.isRequired,
    obj:PropTypes.shape({
        color:PropTypes.string.isRequired,
        fontSize:PropTypes.number
    }).isRequired,
    age:PropTypes.number
}

  • 如果有类型错误,会清晰提
    React基础知识_第18张图片

props默认值

作用:给 props 设置默认值,在未传入 props 时生效

实现方式:

export default class App extends Component {
    state = {
        outdata :"App根组件的数据"
    }

    render() {
        return (
            <div>
                {/* 组件使用组件外部的数据,使用标签属性的方式传递 */}
                <Module1 
                odata={this.state.outdata}
                arr = {[1,3,5,7,6]}
                obj = {{
                    color:"red",
                    fontSize:20
                }}
                str = {"string"}
                num = {1}
                // 刻意不传
                // age={""}
                />
            </div>
        )
    }
}

props默认值

// props效验的作用:就是传入props的时候,传入缺失了一些属性,或属性的值类型错误.可以报出更清晰的错误
Module1.propTypes = {
    arr:PropTypes.array.isRequired,
    str:PropTypes.string,
    num:PropTypes.number.isRequired,
    obj:PropTypes.shape({
        color:PropTypes.string.isRequired,
        fontSize:PropTypes.number
    }).isRequired,
    age:PropTypes.number
}

// 给Module添加属性
// 不传用默认值,传了用传递的值
Module1.defaultProps = {
    age :20
}

React基础知识_第19张图片

表单处理

受控组件

HTML 中的表单元素是可输入的,也就是有自己的可变状态.而React 中可变状态通常保存在 state 中,并且只能通过 setState() 方法来修改 .React将 state 与表单元素值value绑定到一起,由 state 的值来控制表单元素的值

受控组件:其值受到 React 控制的表单元素
组件中有表单项,表单项的值被组件的状态所控制

实现方式:

  1. 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
  2. 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
class Hello extends React.Component {
    constructor() {
        super()
        this.state = { text: '' } // 初始化state
    }

    handle = e => {
        //这里this指向当前组件实例
        this.setState({
            text: e.target.value
        })
    }

    render() {
        return <input onChange={this.handle} value={this.state.text}></input>
    }
}

//补充: 文本框、文本域、下拉框 操作value属性 复选框 操作checked属性 

各种表单元素实现

import React from "react"

// 受控组件:组件中有表单项,表单项的值被组件的状态所控制
export default class Module extends React.Component{
    state = {
        val: "文本框默认值",
        areaval: "文本域区域",
        sele: "5",
        checked:false,
        radio:""


    }

    handleChange = (event)=>{
        this.setState({
            val : event.target.value,
            
        })
    }

    handleChangeArea = (event) => {
        this.setState({
            areaval : event.target.value
        })
    }

    handleChangesele = (event) =>{
        this.setState({
            sele : event.target.value
        })
    }

    handleChangechecked = (event)=>{
        this.setState({
            checked :event.target.checked
        })
    }

    handleChangeradio = (event)=>{
        this.setState({
            radio : event.target.value
        })
    }
    render(){
        return(
            <div>
                {/* 文本框 */}
                <input 
                    type="text"
                    value={this.state.val}
                    onChange={this.handleChange}
                />

                {/* 文本域 */}
                <textarea name="" id="" cols="30" rows="10"
                    value = {this.state.areaval}
                    onChange = {this.handleChangeArea}
                ></textarea>

                {/* 下拉框 */}
                <select name="" id="" 
                    value={this.state.sele}
                    onChange={this.handleChangesele}
                >
                    <option value="1">北京</option>
                    <option value="2">上海</option>
                    <option value="3">广州</option>
                    <option value="4">深圳</option>
                    <option value="5">西藏</option>
                    <option value="6">美国</option>
                </select>

                {/* 复选框 */}
                <input type="checkbox"
                    checked = {this.state.checked}
                    onChange = {this.handleChangechecked}
                />

                {/* 单选框 */}
                <input type="radio" 
                    name = "gender"
                    type = "radio"
                    value = {"女"}
                    onChange = {this.handleChangeradio}
                />

                <input type="text"
                    name = "gender"
                    type = "radio"
                    value = {"男"}
                    onChange = {this.handleChangeradio}
                />

                <div>{this.state.radio}</div>
            </div>
        )
    }
}

多表单元素处理/柯里化

  • 利用柯里化函数实现

柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

import React from "react"

// 受控组件:组件中有表单项,表单项的值被组件的状态所控制
export default class Module extends React.Component{
    state = {
        val: "文本框默认值",
        areaval: "文本域区域",
        sele: "5",
        checked:false,
        radio:""


    }

    // 函数柯里化 共同使用的事件处理函数
    handleChange = name => event => {
            const result = name === "checked"? event.target.checked : event.target.value
            this.setState({
                [name] : result
            })
    }
    /* 
        handleChange = (name) => {
            return (event) => {
                const result = name === "checked"? event.target.checked : event.target.value
                this.setState({
                    [name] : result
                })
            }
        }
    */
   /* 
        fn = a => {
        return b => {
            return c => {
                return d => {

                }
            }
        }
    }

    fn = a => b => c => d => {

    }
   */
    render(){
        return(
            <div>
                {/* 文本框 */}
                <input 
                    type="text"
                    value={this.state.val}
                    onChange={this.handleChange("val")}
                />

                {/* 文本域 */}
                <textarea name="" id="" cols="30" rows="10"
                    value = {this.state.areaval}
                    onChange = {this.handleChange("areaval")}
                ></textarea>

                {/* 下拉框 */}
                <select name="" id="" 
                    value={this.state.sele}
                    onChange={this.handleChange("sele")}
                >
                    <option value="1">北京</option>
                    <option value="2">上海</option>
                    <option value="3">广州</option>
                    <option value="4">深圳</option>
                    <option value="5">西藏</option>
                    <option value="6">美国</option>
                </select>

                {/* 复选框 */}
                <input type="checkbox"
                    checked = {this.state.checked}
                    onChange = {this.handleChange("checked")}
                />

                {/* 单选框 */}
                <input type="radio" 
                    name = "gender"
                    type = "radio"
                    value = {"女"}
                    onChange = {this.handleChange("radio")}
                />

                <input type="text"
                    name = "gender"
                    type = "radio"
                    value = {"男"}
                    onChange = {this.handleChange("radio")}
                />

                <div>{this.state.radio}</div>
            </div>
        )
    }
}

React基础知识_第20张图片

非受控组件

借助于 ref,使用原生 DOM 方式来获取表单元素值

ref 的作用:获取 DOM 或组件

实现方式:

  1. 调用 React.createRef() 方法创建一个 ref 对象

    constructor() { 
      super() 
      this.txtRef = React.createRef() 
    }
    
  2. 将创建好的 ref 对象添加到文本框中

    <input type="text" ref={this.txtRef} />
    
  3. 通过 ref 对象获取到文本框的值

    Console.log(this.txtRef.current.value)
    

组件的生命周期

生命周期图谱

React基础知识_第21张图片

组件的生命周期三个大阶段

挂载阶段

constructor ==> render ==> componentDidMount

constructor: 创建组件时,最先执行 . 一般用于: 1. 初始化state 2. 为事件处理程序绑定this

render: 每次组件渲染都会触发 注意: 不能在render中调用setState()

componentDidMount: 组件挂载(完成DOM)渲染后 一般用于: 1. 发送网络请求 2. DOM操作

更新阶段

render ==> componentDidUpdate

setState() , forceUpdate(), 组件接收到新的props 都会导致更新

componentDidUpdate: 组件更新(完成DOM渲染)后

1 发送网络请求 2 DOM操作 注意:如果要setState() 必须放在一个if条件中

卸载阶段

componentWillUnmount

**componentWillUnmount:**组件卸载(从页面中消失) 执行清理操作

代码演示

import React, { Component } from "react";

// 注意:只有类组件才有生命周期钩子函数,函数组件没有
export default class Test extends Component {
  constructor() {
    super();
    this.state = {
      count: 0,
    };
    console.log("组件创建阶段 constructor");
  }

  render() {
    console.log("render函数 -组件创建以及更新阶段都会调用");
    return (
      <div>
        <div>我是Test组件,{this.state.count}</div>
        <h2>我是Appp组件过来数据,{this.props.Appp}</h2>
        <button
          onClick={() => {
            this.setState({
              count: this.state.count + 1,
            });
          }}
        >
          点击按钮,更新Test组件状态 count加一
        </button>
      </div>
    );
  }

  componentDidMount() {
    console.log("组件创建阶段-componentDidMount-创建完成以后执行,只会执行一次");
  }

  componentDidUpdate(prevProps, prevState) {
    console.log("更新阶段-componentDidUpdate", prevProps, prevState);
  }

  componentWillUnmount() {
    console.log("卸载阶段-componentWillUnMount");
  }
}


刚上来结果
React基础知识_第22张图片

点击以后
第一个参数是传入的props 第二个参数是自己的状态 都是上一次的
React基础知识_第23张图片
卸载

import React, { Component } from 'react'


import Test from "./Test"
export default class App extends Component {
    state ={
        Appp:"这是根组件state数据",
        num:123456
    }
    render() {
        return (
            <div>
                {this.state.num && <Test Appp={this.state.Appp} num={this.state.num}/>}
                <button onClick={()=>{
                    this.setState({
                        num:""
                    })
                }}>App组件按钮,点击让Test卸载</button>
            </div>
        )
    }
}

React基础知识_第24张图片
点击按钮以后
React基础知识_第25张图片

旧版react生命周期(了解)

componentWillMount

componentWillUpdate,

componentWillReceiveProps

以上生命周期钩子函数在React v16.3后废弃

React基础知识_第26张图片

组件通讯

react组件通讯有三种方式.分别是props, context, pubsub

props

单向数据流 父传子

React基础知识_第27张图片

context

  • 爷孙嵌套 传递

React基础知识_第28张图片

实现一:

  • 调用 React. createContext(“默认值”) 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
  • 注意: 默认值是在没有提供provider的时候生效,而不是没有写value的时候生效
  • content

注意: 不管导入多少次. context.js里面的代码只执行一次.这样就可以断定其他文件中导入context对象和这个context是同一个
如果在每个页面创建React.reactContext那么不是同一个对象

import React from "react";
// 默认值是在不写provider的时候生效
const context = React.createContext("哈哈哈哈哈");
export default context;
  • 使用 Provider 组件作为父节点, 使用value属性定义要传递的值 App
import React, { Component } from "react";

import context from "./context";
// 引入父组件
import Far from "./page/Far";

export default class App extends Component {
  state = {
    msg: "我是App的state.msg",
  };
  render() {
    return (
        // 不管哪种方式,不写外面这个context.Provider就有默认值("哈哈哈哈")
      <context.Provider value={this.state.msg}>
        <div>
          顶级--App组件 引入far<Far></Far>
        </div>
      </context.Provider>
    );
  }
}

  • context

  • Far

import React, { Component } from "react";

// 引入子组件
import Son from "./Son";
export default class Far extends Component {
  render() {
    return (
      <div>
        父组件 -- 我是Far组件,引入son组件
        <Son></Son>
      </div>
    );
  }
}

-使用Consumer组件接收数据 Son

import React, { Component } from "react";

import context from "../context";

export default class Son extends Component {
  render() {
    return (
      <context.Consumer>
        {(data) => <div>Son组件, {data}</div>}
      </context.Consumer>
    );
  }
}

实现二

import React, { Component } from "react";

import context from "../context";

export default class Son extends Component {
  // 给要使用Context的Son类,添加静态contextType属性,并赋值为context对象
  // 那么Son的实例对象上context属性中就可以获取到需要的值

  // 这是给当前组件添加静态属性的一种简写形式
  static contextType = context;
  render() {
    console.log(this);
    return <div>Son组件,{this.context}</div>;
  }
}

Son.contextType = context;

React基础知识_第29张图片

**注意:**不要使用context随意传递数据,一般用于传递"全局"数据, 比如当前认证的用户、主题或首选语言

pubsub

发布订阅机制

pubsubjs是一个用JavaScript编写的库。

利用订阅发布模式, 当一个组件的状态发生了变化,可以让其他多个组件更新这些变化.

React基础知识_第30张图片

实现:

安装

在项目根目录下: npm install pubsub-js / yarn add pubsub-js

导入

import PubSub from "pubsub-js" // 导入的PubSub是一个对象.提供了发布/订阅的功能
  • pubsubjs提供的方法

PubSub.publish(TOPIC, ‘hello world!’)

PubSub.subscribe()

PubSub.unsubscribe(token);

PubSub.clearAllSubscriptions();

//  一个用于接收订阅信息的函数(接收外部传入的数据的函数)
// PubSub.subscribe() 用于订阅信息(相当于监听某个组件内部数据变化)
// TOPIC ==> 订阅话题.推荐使用常量
// 第二个参数: 用于接收数据的函数
// token 这一次订阅的令牌(用于取消订阅)
var token = PubSub.subscribe(TOPIC, function (msg, data) {
    console.log( msg, data );
});

// 以异步的形式的发布一个话题 
// TOPIC 通过这个话题,找到订阅这个话题的订阅者
// 'hello world!' 具体要传递的数据
PubSub.publish(TOPIC, 'hello world!');
// 发布的同步使用方法
// 慎用!!!! 因为会阻塞发布者的代码执行
PubSub.publishSync(TOPIC, 'hello world!');

// 取消指定的订阅
PubSub.unsubscribe(token);

// 取消某个话题的所有订阅
PubSub.unsubscribe(TOPIC);

// 取消所有订阅
PubSub.clearAllSubscriptions();

  • App
import React, { Component } from "react";

// 每个使用的页面都要引入pubsub
import PubSub from "pubsub-js";

import Far from "./page/Far";
export default class App extends Component {
  componentDidMount() {
    this.token = PubSub.subscribe("G63", function (msg, data) {
      console.log("我是App订阅的消息:" + msg, data);
    });
  }

  //   这个是只取消当前的订阅者
  cancel = () => {
    PubSub.unsubscribe(this.token);
  };
  render() {
    return (
      <div>
        我是App组件
        <Far></Far>
        {/* 发布 */}
        <button
          onClick={() => {
            PubSub.publish("rollsroyce", "N74B66 V12");
          }}
        >
          App--发布信息--publish
        </button>
        {/* 取消 */}
        <button onClick={this.cancel}>App--取消订阅G63话题</button>
      </div>
    );
  }
}

  • Far
import React, { Component } from "react";

// 每个使用的页面都要引入pubsub
import PubSub from "pubsub-js";
import Son from "./Son";

export default class Far extends Component {
  // 一般组件都是在组件挂载成功之后订阅
  // 当订阅的话题发布了之后,回调函数会被触发
  componentDidMount() {
    PubSub.subscribe("G63", function (msg, data) {
      console.log("Far组件接收到的:" + msg, data);
    });
  }

  render() {
    return (
      <div>
        Far组件
        <Son></Son>
        {/* 这种方式所有订阅这个消息的人 全都取消订阅 */}
        <button
          onClick={() => {
            PubSub.unsubscribe("G63");
          }}
        >
          Far--取消所有G63话题
        </button>
        {/* 这个取消不管写在哪,所有的订阅全都取消 */}
        <button
          onClick={() => {
            PubSub.clearAllSubscriptions();
          }}
        >
          取消所有发布的订阅
        </button>
      </div>
    );
  }
}

  • Son
import React, { Component } from "react";

// // 每个使用的页面都要引入pubsub
import PubSub from "pubsub-js";

export default class Son extends Component {
  // Son发布信息函数
  handle = () => {
    PubSub.publish("G63", "梅赛德斯奔驰AMG");
  };
  // 订阅App消息
  componentDidMount() {
    PubSub.subscribe("rollsroyce", (msg, data) => {
      console.log("我是Son订阅的消息:" + msg, data);
    });
  }
  render() {
    return (
      <div>
        Son组件,
        <button onClick={this.handle}>Son--发布信息--G63</button>
      </div>
    );
  }
}

Fragment

react组件中只能有一个根组件.

之前使用div包裹的方式会给html结构增加很多无用的层级

为了解决这个问题,可以使用React.Fragment

function Hello(){
    return (
      // 渲染到页面之后,这个div就是一个多余的
      <div>
        <h1>fragment</h1>
        <p>hello react</p>
      </div>
    ) 
}
// 将上面的写法,修改为下面的写法
function Hello(){
    return (
      // 这样就只会渲染h1和p
      <React.Fragment>
        <h1>fragment</h1>
        <p>hello react</p>
      </React.Fragment>
    ) 
}

// 简写形式
function Hello(){
    return (
      // 这是React.Fragment的简写形式
      <>
        <h1>fragment</h1>
        <p>hello react</p>
      </>
    ) 
}

React性能优化

  • 减轻state 跟页面渲染无关的数据,不要放在state中(比如定时器的id.不要放在state中)

    • 可以有效减少页面重新渲染次数
  • shouldComponentUpdate (减轻不必要的重新渲染)

    • **组件更新的机制: **父组件重新渲染时,也会重新渲染子组件

    • 如何避免不必要的重新渲染呢?

shouldComponentUpdate(nextProps,nextState)

  • App
import React, { Component } from "react";
import Son from "./Son"
export default class App extends Component {
  state = {
    count: 0,
  };

  handle = () => {
    this.setState({
      // count: this.state.count + 1
      count: 0

    });
  };
  render() {
      console.log("App组件更新了")
    return (
      <>
        <div>APP组件</div>
        <h1>呵呵</h1>
        <h2>哈哈哈</h2>
        <Son count={this.state.count}></Son>
        <button onClick={this.handle}>按钮,改变APP数据状态</button>
      </>
    );
  }
}

  • Son
import React, { Component } from "react";

export default class Son extends Component {
  state = {
    msg: "Sun旧的state.msg",
    obj: "rolls",
  };
  // 这个函数是传入的新的props以及state更新的时候会被触发
  // 这个函数要求返回一个boolean值,返回true,就执行render. 返回false就不执行
    // shouldComponentUpdate 只有在更新阶段才会被调用
  shouldComponentUpdate(nextProps, nextState) {
    console.log("Son组件的 shouldComponentUpdate执行了");
    //  console.log(nextProps)  最新的props的值 {count: 1}
    //  console.log(this.props) 旧的props的值 {count: 1}

    // console.log(nextState) 最新的state值 {msg: "Sun旧的state.msg", obj: "rolls"}
    // console.log(this.state) 旧的state的值

    if (
      nextProps.count !== this.props.count ||
      nextState.msg !== this.state.msg
    ) {
      return true;
    }
    return false;
  }
  handleSon = () => {
    this.setState({
      msg: "Son新的state.msg",
    });
  };
  render() {
    console.log("Son组件更新了");
    return (
      <>
        {/* 如果上面函数returnfalse  这个App传过来的值不会变 */}
        <h1>Son组件,{this.props.count}</h1>
        <button onClick={this.handleSon}>修改Son的状态数据</button>
      </>
    );
  }
}

React基础知识_第31张图片

  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)

  • 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染

  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate => render)

```javascript
// 父组件
class Far extends Component {
  state = {
    num: 0
  }
  // 返回min~max之间的随机整数(包含min和max)
  getRandomIntInclusive(min, max) {
    min = Math.ceil(min)
    max = Math.floor(max)
    return Math.floor(Math.random() * (max - min + 1)) + min //含最大值,含最小值
  }
  handle = () => {
    let num = this.getRandomIntInclusive(1, 3)
    this.setState({
      num
    })
  }
  //有效减少组件渲染次数
  shouldComponentUpdate(props, state) {
    // this.state 原来的state
    // state 最新的state
    return this.state.num !== state.num 
  }
  render() {
    console.log('父组件渲染了')
    return (
      

Far组件

{this.state.num}

) } } // 不管子组件有没有用到父组件的数据,只要父组件重新渲染了,子组件就会跟着渲染 // 子组件 class Son extends Component { //有效减少组件渲染次数 shouldComponentUpdate(props, state) { // this.props 原来的props // props 最新的props return props.num !== this.props.num } render() { console.log('Son组件渲染了') return (

Son组件

{this.props.num}

) } } ```

纯组件 pureComponet

  • PureComponent 与 React.Component 功能相似,直接替换即可

  • 区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较

  • 原理:纯组件内部通过分别 对比 前后两次 props 和 state 的值,来决定是否重新渲染组件

  • **注意: **纯组件内部的对比是 shallow compare(浅层对比)

    • 对于引用类型来说:只比较对象的引用(地址)是否相同
    • 所以,state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据
  • App

import React, { Component } from "react";
import Son from "./Son"
export default class App extends Component {
  state = {
    count: 0,
  };

  handle = () => {
    this.setState({
      // count: this.state.count + 1
      count: 0

    });
  };
  render() {
      console.log("App组件更新了")
    return (
      <>
        <div>APP组件</div>
        <h1>呵呵</h1>
        <h2>哈哈哈</h2>
        <Son count={this.state.count}></Son>
        <button onClick={this.handle}>按钮,改变APP数据状态</button>
      </>
    );
  }
}

- Son
import React, { PureComponent } from "react";

export default class Son extends PureComponent {
  state = {
    msg: "Sun旧的state.msg",
    obj: {
        name :  "rolls",
    }
  };

  handleSon = () => {
    let obj = { ...this.state.obj };
    obj.name = "royce";
    this.setState({
      obj,
    });
  };
  
  render() {
    console.log("Son组件更新了");
    return (
      <>
        {/* 如果上面函数returnfalse  这个App传过来的值不会变 */}
        <h1>{this.state.obj.name}</h1>
        <h1>Son组件,{this.props.count}</h1>
        <button onClick={this.handleSon}>修改Son的状态数据</button>
      </>
    );
  }
}

等待删除

```javascript
class Far extends PureComponent {
  state = {
    obj: {
      num: 0
    }
  }

  getRandomIntInclusive(min, max) {
    min = Math.ceil(min)
    max = Math.floor(max)
    return Math.floor(Math.random() * (max - min + 1)) + min //含最大值,含最小值
  }
  handle = () => {
    // 错误的写法
    // 重新设置的obj和上一次的obj是同一个.
    // 组件不会重新渲染了
    // let num = this.getRandomIntInclusive(1, 3)
    // let obj = this.state.obj 或者 let {obj} = this.state
    // obj.num = num
     
    // 正确的写法:
    let num = this.getRandomIntInclusive(1, 3)
    // 创建一个新的对象
    let obj = { num }
    console.log(num)
    this.setState({
      obj
    })
  }

  render() {
    console.log('父组件渲染了')
    return (
      

Far组件

{this.state.obj.num}

) } } ```

React.forwardRef(不常用.可以通过props传递)

函数组件不可以添加ref属性

作用: 将函数组件内部的元素,交给父组件使用

// 实现: 
// App组件
const ref = React.createRef()
class App extends React.Component {
  componentDidMount() {
    // 在app组件中,通过ref获取到了button的Dom对象
    console.log(ref.current)
  }
  render() {
    return (
      <div>
        // 使用React.forwardRef包装后的FancyButton就可以添加ref属性了
        <FancyButton ref={ref}>Click me!</FancyButton>
      </div>
    )
  }
}
// FancyButton
const FancyButton = React.forwardRef((props, ref) => {
  console.log(props, ref)
  return (
    <button ref={ref} className='FancyButton'>
      {props.children}
    </button>
  )
})

// 你可以直接获取 DOM button 的 ref:
const ref = React.createRef();
<FancyButton ref={ref}>Click me!</FancyButton>;

Portal

Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案

App.js

import React, { Component } from "react";

import Test from "./Test";
export default class App extends Component {
  render() {
    return (
      <div>
        <p>1</p>
        {/* 在app组件中使用了portal组件
		 但是渲染dom的时候,portal的内容不在这个div中 */}
        <Test />
        <div>2</div>
      </div>
    );
  }
}


Test.js

import React, { Component } from "react";
import ReactDOM from "react-dom";

export default class Test extends Component {
  constructor() {
    super();
    this.rollsroyce = document.createElement("div");
  }

  componentDidMount() {
    document.body.appendChild(this.rollsroyce);
  }

  render() {
    let node = (
      <div>
        <h1>Test组件</h1>
      </div>
    );
    // 当前portal组件不直接返回元素,而是返回一个portal
    // react底层会将node的内容,渲染到this.rollsroyce中
    return ReactDOM.createPortal(node, this.rollsroyce);
  }
}

React基础知识_第32张图片

React基础知识_第33张图片

高阶组件(HOC higherOrderComponent)

  • **高阶组件的作用: ** 提供复用的状态逻辑

  • 高阶组件是什么: 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式

    • 简单理解的话: 一个拥有复用逻辑的函数,这个函数需要传入一个组件,然后返回一个增强的组件
  • 高阶组件的基本逻辑
    开发者要去定义个函数:函数名一般以With开头
    const 新的组件 = Withxxx(需要公用状态和逻辑的组件)
    const Withxxx = 新组件(基础组件) 这个新的组件中有公共的状态和逻辑
    后期长期使用新的组件

const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件实现

  1. 调用函数,得到增强组件,渲染增强组件
  2. 函数名一般以with开头,使用小驼峰命名法
  3. 函数中形参要采用大驼峰命名法(因为这个参数接收的是一个组件)
  4. 返回的也是一个组件,所以也要使用大驼峰命名法
  • App
import React, { Component } from "react";
import Login from "./pages/Login";
import Register from "./pages/Register";

// 引入定义的高阶函数
import WithForm from "./WithForm";

// 调用高阶函数以后得到一个增强后的新组件
const WithLogin = WithForm(Login);
const WithRegister = WithForm(Register);
export default class App extends Component {
  state = {
    msg: "我是APP里面的数据",
  };
  render() {
    return (
      <div>
        <h1>高阶组件</h1>
        {/* 
         */}

        {/* 这些数据只是传递给高阶组件,将来还要再次传递给写的组件 */}
        <WithLogin {...this.state} />
        <WithRegister test={"数据最终会在register props上"} />
      </div>
    );
  }
}

  • Form 高阶组件
import React from "react";

// 注意:WithForm函数的形参,应该首字母大写,因为这个形参接收的是一个组件,后面要直接使用这个组件
export default function WithForm(WrappedComponent) {
  return class extends React.Component {
    /* 
        如果组件不写名字(匿名组件,可以这样),在react-dev-tool中默认展示_temp
        如果组件设置了dispalyName静态属性,那么react-dev-tool中展示的组件名就是displayName的值
    */
    static displayName = "With" + WrappedComponent.name;
    state = {
      username: "",
      password: "",
      repassword: "",
    };

    handleChange = (name) => (event) => {
      this.setState({
        [name]: event.target.value,
      });
    };

    render() {
      return (
        <WrappedComponent
          {...this.state}
          /* 
            这个相当于
            username={this.state.username}
            password={this.state.password}
            repassword={this.state.repassword}
          */
          handleChange={this.handleChange}
          //  如果不把WithLogin上传的props传递给login,login上是没有这些数据的
          {...this.props}
        ></WrappedComponent>
      );
    }
  };
}

  • login
import React, { Component } from "react";

export default class Login extends Component {
  render() {
    const { username, password, handleChange } = this.props;
    return (
      <div>
        <div>{username}</div>
        <div>{password}</div>
        <h1>登录</h1>
        <form>
          用户名:
          <input
            type="text"
            value={username}
            onChange={handleChange("username")}
          />
          <br />
          密码:
          <input
            type="password"
            value={password}
            onChange={handleChange("password")}
          />
          <br />
          <input type="button" value="登录" />
        </form>
      </div>
    );
  }
}

  • register
import React, { Component } from "react";

export default class Register extends Component {
  render() {
    const { username, password, repassword, handleChange } = this.props;
    return (
      <div>
        <div>{username}</div>
        <div>{password}</div>
        <div>{repassword}</div>
        <h1>注册</h1>
        <form>
          用户名:
          <input
            type="text"
            value={username}
            onChange={handleChange("username")}
          />
          <br />
          密码:
          <input
            type="password"
            value={password}
            onChange={handleChange("password")}
          />
          <br />
          确认密码:
          <input
            type="password"
            value={repassword}
            onChange={handleChange("repassword")}
          />
          <br />
          <input type="button" value="登录" />
        </form>
      </div>
    );
  }
}

React基础知识_第34张图片
React基础知识_第35张图片

  1. 使用es7的修饰符
@higherOrderComponent
class WrappedComponent extends React.Component
  • 高阶组件要注意的问题:

组件名问题

  1. 配合chrome调试工具显示组件名的问题

React基础知识_第36张图片

**解决: **

给高阶组件中返回的组件, 增加一个静态属性displayName

static displayName = `XXX(${WrappedComponent.displayName ||WrappedComponent.name ||'Component'})`

//原理: 调试工具,优先展示组件的displayName

传递props的问题

解决办法见上面的
React基础知识_第37张图片

React基础知识_第38张图片

render props

**render props的作用: **提供共享的状态逻辑

**render props是什么: **指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

简单理解: 实现多个组件状态和逻辑共享的技术(复用)

export default class App extends Component {
  render() {
    return (
      <div>
        // 原来直接在这里渲染Cat和Mouse组件
        // 使用了render props技术之后, 在这里使用封装了共享状态逻辑的组件,
        // 真正要渲染的Cat和Mouse需要当做render这个prop的返回值传进去
        <Position render={pos => <Cat {...pos} />}></Position>
        <Position render={pos => <Mouse {...pos} />}></Position>
      </div>
    )
  }
}

return this.props.render()方式

  1. 将复用的状态和逻辑代码封装到一个组件中 (比如命名为Position)

Position

import { Component } from "react";

/* 
  利用render props技术定义的一个组件

  render props技术需要开发者定义一个组件,使用这个组件
*/
export default class Position extends Component {
  // 公用的状态
  state = {
    x: 0,
    y: 0,
  };

  handleMove = (event) => {
    // 获取到鼠标的坐标,然后赋值给state (根据需求逻辑定义共享逻辑代码)
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };

  componentDidMount() {
    // 组件挂载成功,注册事件,监听鼠标移动
    window.addEventListener("mousemove", this.handleMove);
  }

  componentWillUnmount() {
    // 组件卸载的时候,要移除事件
    window.removeEventListener("mousemove", this.handleMove);
  }
  render() {
    /* 
      this.props.render拿到的就是一个回调函数
      将鼠标的坐标,当做实参传入到render函数的位置上
      this.props.render函数调用完毕之后,可以获取到对应想要渲染的组件
    */
    return this.props.render(this.state);
  }
}

App

import React, { Component } from "react";
import Cat from "./components/Cat";
import Mouse from "./components/Mouse";

import Position from "./Position";

export default class App extends Component {
  render() {
    return (
      <div>
        <h1>猫抓老鼠</h1>
        {/* 
         */}

        {/* renderprops技术, 要求在使用Position组件的时候加一个render属性,render属性的值必须是一个回调函数*/}
        {/* 这里使用的是postion组件,这个组件返回render,回调函数返回mouse组件,实际上回调函数返回谁,这个位置最终渲染谁的结构 */}
        <Position render={(state) => <Mouse xxx={state}></Mouse>}></Position>
        <Position render={(state) => <Cat xxx={state}></Cat>}></Position>
      </div>
    );
  }
}

Mouse

import React, { Component } from "react";

// react脚手架,不可以直接写图片的相对路径,需要导入一下
import MouseUrl from "../assets/mouse.gif";

export default class Mouse extends Component {
  render() {
    let { x, y } = this.props.xxx;
    return (
      <div>
        <img
          src={MouseUrl}
          alt=""
          style={{ position: "absolute", left: x, top: y, width: 100 }}
        />
      </div>
    );
  }
}

Cat

import React, { Component } from "react";

// react脚手架,不可以直接写图片的相对路径,需要导入一下
import CatUrl from "../assets/cat.gif";

export default class Cat extends Component {
  render() {
    let { x, y } = this.props.xxx;
    return (
      <div>
        <img
          src={CatUrl}
          alt=""
          style={{ position: "absolute", left: x, top: y }}
        />
      </div>
    );
  }
}

return this.props.children()方式

Position

import { Component } from "react";

/* 
  利用render props技术定义的一个组件

  render props技术需要开发者定义一个组件,使用这个组件
*/
export default class Position extends Component {
  // 公用的状态
  state = {
    x: 0,
    y: 0,
  };

  handleMove = (event) => {
    // 获取到鼠标的坐标,然后赋值给state (根据需求逻辑定义共享逻辑代码)
    this.setState({
      x: event.clientX,
      y: event.clientY,
    });
  };

  componentDidMount() {
    // 组件挂载成功,注册事件,监听鼠标移动
    window.addEventListener("mousemove", this.handleMove);
  }

  componentWillUnmount() {
    // 组件卸载的时候,要移除事件
    window.removeEventListener("mousemove", this.handleMove);
  }
  render() {
    /* 
      this.props.children拿到的就是一个回调函数 标签内的回调函数,给这个函数传参数
      特别注意:
      如果 {(state) => }
      Position里面没有空格 name直接获取的是state这个回调函数

      如果Position标签里面的回调和Position有空格,获取的是一个数组,包括空白文档,必须加下标1拿到的才是这个回调
    */

    return this.props.children(this.state);
  }
}

App

import React, { Component } from "react";
import Cat from "./components/Cat";
import Mouse from "./components/Mouse";

import Position from "./Position";

export default class App extends Component {
  render() {
    return (
      <div>
        <h1>猫抓老鼠</h1>
        {/* 
         */}

        {/* renderprops技术, 要求在使用Position组件的时候加一个render属性,render属性的值必须是一个回调函数*/}
        {/* 这里使用的是postion组件,这个组件返回render,回调函数返回mouse组件,实际上回调函数返回谁,这个位置最终渲染谁的结构 */}
        {/* 第二种方式直接把这个回调函数写在position标签内部 */}

        {/* 方式一 */}
        <Position>{(state) => <Mouse xxx={state}></Mouse>}</Position>

        {/* 建议使用方式二,因为你不知道他会传入多少个参数 */}
        <Position>{(state) => <Cat {...state}></Cat>}</Position>

        {/* 千万注意不要有空格,否则this.props.children获取到的是一个数组 */}
        {/*  {(state) => }  */}
      </div>
    );
  }
}

Cat

import React, { Component } from "react";

// react脚手架,不可以直接写图片的相对路径,需要导入一下
import CatUrl from "../assets/cat.gif";

export default class Cat extends Component {
  render() {
    // 方式二的接收方式
    let { x, y } = this.props;
    return (
      <div>
        <img
          src={CatUrl}
          alt=""
          style={{ position: "absolute", left: x, top: y }}
        />
      </div>
    );
  }
}

Mouse

import React, { Component } from "react";

// react脚手架,不可以直接写图片的相对路径,需要导入一下
import MouseUrl from "../assets/mouse.gif";

export default class Mouse extends Component {
  render() {
    // 方式一的接收方式
    let { x, y } = this.props.xxx;
    return (
      <div>
        <img
          src={MouseUrl}
          alt=""
          style={{ position: "absolute", left: x, top: y, width: 100 }}
        />
      </div>
    );
  }
}

Hooks

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

  • Hook也叫钩子,本质就是函数,能让你使用 React 组件的状态和生命周期函数…

  • Hook 还没有完全替代class,但是未来会逐步替代

为什么有Hook?

  • 组件复用状态逻辑比较难 (HOC和render props 写起来比较麻烦),Hook 使你在无需修改组件结构的情况下复用状态逻辑

  • 解决了this难以理解的问题

  • 希望可以逐渐取代class

useState()基本使用

  • 可以在单个组件中,多次使用
import React, { useState } from "react";

export default function Add() {
  /*  
  	  useState可以在函数组件中使用状态
      返回一个数组,数组中可以解构一个数据和一个操作数据的方法
      setCount是用来操作count的方法
      useState的值表示count的初始化值
      在后续的重新渲染中 useState返回的第一个值将始终是更新后最新的state
  */

  const [count, setCount] = useState(0);
  const [num, setNum] = useState(100);

  function handleCount() {
    setCount(count + 1);
  }

  function handleNum() {
    setNum(num - 1);
  }
  return (
    <>
      <div>{count}</div>
      <div>{num}</div>
      <button onClick={handleCount}>点击按钮count+1</button>
      <button onClick={handleNum}>点击按钮Num减1</button>
    </>
  );
}

  • 相当于下面的效果
import React, { Component } from "react";

export default class Addplus extends Component {
  state = {
    count: 0,
    num: 100,
  };

  handleCount = () => {
    this.setState({
      count: this.state.count + 1,
    });
  };
  handleNum = () => {
    this.setState({
      num: this.state.num - 1,
    });
  };

  render() {
    return (
      <>
        <div>{this.state.count}</div>
        <div>{this.state.num}</div>
        <button onClick={this.handleCount}>点击按钮count+1</button>
        <button onClick={this.handleNum}>点击按钮Num减1</button>
      </>
    );
  }
}

useEffect() 基本使用

可以在单个组件中多次使用

这个Hook,相当于componentDidMount, componentDidUpdate和componentWillUnmount的组合

  1. 从react包中导出
    useEffect(()=>{
    return ()=>{}
    },[])

第一个参数: 默认模拟的是组件挂载和组件更新

  1. 如果第二个参数: 写了一个空的数组. 第一个参数只模拟组件挂载
    如果中括号中,监听了某些数据.那么第一个参数挂载成功会调用.监听的数据发生变化了,也会调用.但是没有监听的数据,变化了,就不会调用
    第一个参数,可以返回一个回调函数. 这个回调函数.模拟组件即将卸载

useEffect的作用: 就是为了开发者可以在函数组件中,使用生命周期钩子函数

import React, { useState, useEffect } from "react";

// 接收传递过来数据
export default function Add(props) {
  const [count, setCount] = useState(0);
  const [num, setNum] = useState(100);

  /* 
    useEffect作用: 就是为了开发者可以在函数组件中使用声明周期钩子函数
    生命周期钩子函数,这个回调函数相当于是类组件中生命周期钩子函数componentDidMount 和 componentDidUpData
  */
  useEffect(() => {
    console.log("模拟 componentDidMount  组件加载成功");

    // 第一个参数(回调函数)里面可以再return一个函数
    // return的这个函数模拟的是卸载的回调函数 componentWillUnmount
    // 如果没写第二个参数,,,,它的机制就是每次先会卸载掉之前的组件,然后再全部重新渲染
    return () => {
      console.log("模拟 componentWillUnmount 组件卸载了");
    };

    // 如果在第二个参数的位置上传入一个空数组,则这个回调函数只表示componentDidMount
    // 第二个参数里的值,只要监听的数据变化,就会变化 执行componentDidUpdata 一般很少在里面写
  }, [props.msg, num]);

  // 事件处理区域
  function handleCount() {
    setCount(count + 1);
  }

  function handleNum() {
    setNum(num - 1);
  }
  return (
    <>
      <div>{count}</div>
      <div>{num}</div>
      <button onClick={handleCount}>点击按钮count+1</button>
      <button onClick={handleNum}>点击按钮Num减1</button>
    </>
  );
}

Hook规则:

循环/条件/嵌套函数不能

  1. 只在最顶层使用 Hook(函数组件内顶级作用域),不要在循环,条件或嵌套函数中调用 Hook
    • 我们可以在单个组件中使用多个 State Hook 或 Effect Hook, React 靠的是 Hook 调用的顺序,来确定哪个state对应的哪个useState
      React基础知识_第39张图片

React基础知识_第40张图片

普通函数/类组件不能

  • 只能在顶级作用域使用
  • 可以在函数组件中使用,也可以在自定义的hooks中使用
  • 不能在普通函数/类组件中使用

React基础知识_第41张图片

在这里插入图片描述

  • 类组件调用
    React基础知识_第42张图片

官方提供的Hooks介绍

  • 基础 Hook
    • useState
    • useEffect
    • useContext
  • 额外的 Hook
    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

自定义Hooks

myhooks

// 一般自定义hooks的名字都是usexxx

import React, { useState, useEffect } from "react";
export default function usePosition() {
  const [x, setX] = useState(0);
  const [y, setY] = useState(0);

  useEffect(() => {
    // 创建的时候,注册鼠标移动事件
    window.addEventListener("mousemove", handleMove);
    return () => {
      window.removeEventListener("mousemove", handleMove);
    };
  }, []);

  // 注意: 这个函数写在useEffect前面是可以的,但是建议大家,写在所有的hooks后面,这样代码的刻度性更高

  function handleMove(event) {
    setX(event.clientX);
    setY(event.clientY);
  }

  return { x, y };
}

App

import Cat from "./components/Cat";
import Mouse from "./components/Mouse";

export default function App() {
  return (
    <div>
      <h1>猫抓老鼠</h1>
      <Mouse></Mouse>
      <Cat></Cat>
    </div>
  );
}



Cat

import CatUrl from "../assets/cat.gif";

import usePosition from "../myhooks";
export default function Cat() {
  const { x, y } = usePosition();
  return (
    <div>
      <img
        src={CatUrl}
        alt=""
        style={{ position: "absolute", left: x, top: y }}
      />
    </div>
  );
}

Mosue

import MouseUrl from "../assets/mouse.gif";
import usePosition from "../myhooks";
export default function Mouse() {
  const { x, y } = usePosition();
  return (
    <div>
      <img
        src={MouseUrl}
        alt=""
        style={{ position: "absolute", left: x, top: y, width: 100 }}
      />
    </div>
  );
}

你可能感兴趣的:(React,react.js,javascript,前端)