IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React基础 -> 正文阅读

[JavaScript知识库]React基础

文章目录


React官方技术文档: https://reactjs.org/

一、初识React

React是一个用于构建用户界面的JavaScript库,主要用来写HTML页面或构建Web应用。它起源于 Facebook 的内部项目,用来架设 Instagram 的网站,并于 2013 年 5 月开源。React 拥有较高的性能,代码逻辑非常简单。从MVC(M是指业务模型,V是指用户界面,C则是控制器)角度来看,React仅仅是视图层(V),也就是只负责视图的渲染,而并非提供完整的M和C功能

1、React的特点

  • 声明式设计: 只需要描述UI(HTML)看起来式什么样子,就跟写HTML一样。React负责渲染UI,并在数据变化时更新UI

  • 基于组件: 过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中

  • 高效灵活: React通过对DOM的模拟,最大限度地减少与DOM的交互,也可以与已知的库或框架很好地配合

  • JSX: SX 是 JavaScript 语法的扩展,建议在React开发中使用

  • 单向响应数据流: React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单

  • 随处使用: 使用React可以开发Web应用、移动端原生应用(react-native)、VR(react 360)

2、安装React

React 可以直接下载使用,下载包中也提供了很多学习的实例,下载地址 React官网
安装React前需安装node和npm,可使用node -vnpm -v查看是否安装及安装的版本。国内npm较慢,可以设置成淘宝的镜像来代替原有的,操作方法为cmd输入命令npm config set registry https://registry.npm.taobao.org
安装React使用命令npm i react react-dom,这里同时安装了两个包reactreact-dom,其中react包是核心,提供创建元素、组件等功能,react-dom包提供DOM相关功能,二者相互配合

3、我的第一个React实例

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Hello React!</title>
    <!-- 第一步:引入JS文件,这里需要注意引入顺序 -->
    <!-- React核心库 -->
    <script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
    <!-- 提供与DOM相关的功能 -->
    <script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
    <!-- Babel 可以将 ES6 代码转为 ES5 代码,这样我们就能在目前不支持 ES6 浏览器上执行 React 代码 -->
    <script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>

<body>
    <div id="root"></div>
    <!-- 第二步:创建React元素,并渲染React元素 -->
    <script type="text/babel">
    	//创建React元素
        // 参数1:元素名称
        // 参数2:元素属性
        // 参数3:元素的子节点,可以为文本节点,
        const title = React.createElement('h1', null, 'Hello, react!')
        
        // 渲染React元素
        // 参数一:要渲染的React元素
        // 参数二:挂载点
        ReactDOM.render(title, document.getElementById('root'))
    </script>
</body>
</html>

4、React脚手架的使用

4.1 React脚手架的意义

  • 脚手架是开发现代Web应用的必备工具
  • 充分利用Webpack、Babel、ESLint等工具辅助项目开发
  • 零配置,无需手动配置繁琐的工具即可使用

4.2 使用React脚手架初始化项目

  • 初始化项目:命令为npx create-react-app my-app
  • 启动项目:在创建的根目录下执行命令npm start
  • 使用脚手架:使用模块化语法导入react和react-dom包,使用React,此时保存后可以自动重新编译,浏览器自动刷新
  • 不自动刷新的解决方案: 在package.json同级目录下新建一个文件名为.env的文件,并添加FAST_REFRESH=false,并重新npm start
// 第一步:导入React
import React from 'react'
import ReactDOM from 'react-dom'

// 第二步:创建react元素
const title = React.createElement('h1', null, 'Hello, react 脚手!')

// 第三步:渲染react元素
ReactDOM.render(title, document.getElementById('root'))

二、JSX语法

1、JSX的基本使用

1.1 creatElemet()的问题

  • 繁琐不简洁

  • 不直观,无法一眼看出所描述的结构

  • 不优雅,用户体验不爽

creatElemet()方法

React.createElement(
'div',
{className: 'shopping-list'},
React.createElement('h1', null, 'Shopping List'),
React.createElement( 'ul',
null,
React.createElement('li', null, 'Instagram'),
React.createElement('li', null, 'WhatsApp')
) 
)

JSX诞生

<div className="shopping-list">
  <h1>Shopping List</h1>
  <ul>
    <li>Instagram</li>
    <li>WhatsApp</li>
  </ul>
</div>

1.2 JSX简介

React 使用 JSX 来替代常规的 JavaScript,它是一种 JavaScript 的语法扩展,其全称是JavaScript XML。

  • 它是类型安全的,在编译过程中就能发现错误
  • JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化
  • 声明式语法更加直观,与HTML结构相同,使用 JSX 编写模板更加简单快速,降低了学习成本,提升开发效率

1.3 为什么脚手架中可以直接使用JSX语法

  • JSX 不是标准的ECMAScript语法,它是ECMAScript的语法拓展
  • 需要使用babel编译处理后,才能在浏览器环境中使用
  • create-react-app脚手架中已经默认有该配置,无需手动配置
  • 编译JSX语法的包: @bable/preset-react

2、JSX基本语法

2.1 JSX注意点

  • React元素的属性名使用驼峰命名法
  • 特殊属性名:class -> className,for -> htmlFor,tabindex -> tabIndex
  • 如果没有子节点(这里包括文本节点)的React元素可以用 /> 来结束
  • 可以在 JSX 中使用 JavaScript 表达式,表达式写在花括号 {} 中,从而避免JS中自动插入分号报错

2.2 在JSX中嵌入JS表达式

语法: {JavaScritp表达式}

实例:

ReactDOM.render(
    <div>
      <h1>{1+1}</h1>
    </div>
    ,
    document.getElementById('example')
);

注意点:

  • 只要是合法的js表达式都可以进行嵌入
  • JSX自身也是js表达式,也可以放入JSX的JS花括号中
  • js中的对象是例外,一般不合法,但可以出现在style属性
  • 在{}中不能出现语句,如if、for等

样式:

  1. 不推荐使用行内样式
<li key={item.id} style={{'color': 'red',"backgroundColor": 'pink'}}>{item.name}</li>
  1. React 推荐使用内联样式,可以使用 camelCase语法来设置内联样式, React 会在指定元素数字后自动添加 px。
var myStyle = {
    fontSize: 100,
    color: '#FF0000'
};
ReactDOM.render(
    <h1 style = {myStyle}>菜鸟教程</h1>,
    document.getElementById('example')
);

注释:

{/*注释...*/}

数组:

JSX 允许在模板中插入数组,数组会自动展开所有成员:

var arr = [
  <h1>我是数组元素1</h1>,
  <h2>我是数组元素2</h2>,
];
ReactDOM.render(
  <div>{arr}</div>,
  document.getElementById('root')
);

2.3 条件渲染

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

let isLoading = true
let loading = ()=>{
    if(isLoading){
        return <div>Loading...</div>
    }
    return <div>加载完成</div>
}

2.4 列表渲染

  • 如果需要渲染一组数据,我们应该使用数组的map() 方法
  • 渲染列表的时候需要添加key属性,且key属性的值要保证唯一,注意要尽量避免使用索引号作为key(插入、修改数据等操作时可能引发性能问题),渲染的列表中不会有key,其仅用作React内部
  • 原则: map()遍历谁,就给谁添加key属性
// 列表
const songs =[
    {id: 1, name: '两只老虎'},
    {id: 2, name: '小红帽'}{id: 3, name: '小兔子乖乖'}
]

const list = (
    <ul>
        {songs.map(item => <li key={item.id}>{item.name}</li>)}
    </ul>
)

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

三、React组件及事件处理

组件表示页面中的部分功能,组合多个组件实现完整的页面功能,其特点有:可复用、独立、可组合

1、创建组件的两种方式

1.1 使用函数创建组件

1. 函数组件:

使用JS的函数创建组件,直接用函数名作为组件标签名,组件标签可以是单标签,也可以是双标签

2. 两个约定:

  • 函数名称必须大写字母开头,否则会报错
  • 函数组件必须有返回值,表示该组件的结构,如果返回值为null,表示不渲染任何内容
// 创建函数组件
function Hello() {
    return (
        <div>这是我的第一个函数组件</div>
    )
}
// 渲染组件
ReactDOM.render(<Hello />, document.getElementById('root'))

1.2 使用类创建组件

1. 类组件:

使用ES6语法的class创建的组件

2. 四个约定:

  • 类名称必须要大写字母开头
  • 类组件应该继承React.Component父类,从而可以使用父类中提供的方法或者属性
  • 类组件必须提供 render 方法
  • render方法中必须要有return返回值,返回值为null,表示不渲染任何内容
// 创建类组件
class Hello extends React.Component{
    render(){
        return (
            <div>这是我的第一个类组件</div>
        )
    }
}
// 渲染组件
ReactDOM.render(<Hello />, document.getElementById('root'))

1.3 将创建好的组件抽离到JS文件

组件作为一个独立的个体,一般都会放到一个单独的JS文件

实例:

  • 创建Hello.js
  • 在Hello.js 中导入React,创建组件,在Hello.js中导出
import React from 'react'
// 创建组件
class Hello extends React.Component {
    render(){
        return (
            <div>我的第一个单独抽离出来的组件Hello</div>
        )
    }
}
// 导出组件
export default Hello
  • 在index.js中导入Hello组件,渲染到页面
import Hello from './js/Hello'
ReactDOM.render(<Hello />, document.getElementById('root'))

2、React事件绑定和事件对象

2.1 事件绑定

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

  • React 事件绑定属性的命名采用驼峰式写法,而不是小写,如:onMouseEnter
  • 如果采用 JSX 的语法你需要传入一个函数作为事件处理函数,而不是一个字符串(DOM 元素的写法)
  • 语法:on + 事件名称 = { 事件处理程序 },如:onClick = { () => {}}

HTML:

<button onclick="activateLasers()">
  激活按钮了
</button>

React:

<button onClick={activateLasers}>
  激活按钮了
</button>

2.2 事件对象

  • 可以通过事件处理函数的参数e获取到事件对象,而无需为一个已创建的 DOM 元素添加监听器
  • React中的事件对象e是一个合成事件,兼容所有浏览器,无需担心跨浏览器兼容问题

HTML:

<a href="#" onclick="console.log('点击链接'); return false">
  点击按钮,不会触发默认事件
</a>

React:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('链接被点击了');
  }
 
  return (
    <a href="#" onClick={handleClick}>
      点击按钮,不会触发默认事件
    </a>
  );
}

3、React State(状态)

React 把组件看成是一个状态机(State Machines)。状态(State)即数据,通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。React 里,只需更新组件的 state,然后根据新的 state 重新渲染用户界面(不要操作 DOM)。

3.1 有状态组件和无状态组件

  • 函数组件又叫做无状态组件,类组件又叫做有状态组件
  • 函数组件没有自己的状态,只负责数据展示(
  • 类组件有自己的状态,负责更新UI,让页面动起来(动)

计数器就是一个有状态组件:

计数器:0 → 计数器:1

3.2 组件的State和setState

State:

  • 状态(State)即数据,是组件内部的私有数据,只能在组件内部使用
  • state的值是对象,表示一个组件中可以有多个数据
  • 通过this.state来获取状态
export default class extends React.Component {
    constructor(){
        super()
        /* 第一种初始化方式
        this.state = {
            count: 0
        }*/
    }
    // 第二种初始化方式,简化语法,简洁推荐使用
    state = {
        count: 0
    }
	// 使用状态中的数据
    render(){
        return (
            <div>计数器 :{this.state.count}</div>
        )
    }
}

使用setState()修改状态:

  • 状态是可变的,通过this.setState({要修改的数据})修改状态,多个数据只需加入需修改的数据
  • 不要直接修改state中的值,这是错误的,不能改变数据
  • setState() 作用:修改state,并更新UI
  • 其思想是:数据驱动视图
export default class extends React.Component {
    state = {
        count: 0
    }
    render(){
        return (
            <div>
                <div>计数器 :{this.state.count}</div>
                {/* 注:这里使用箭头函数,没有绑定this,其this指向的是函数定义位置的上下文this,此处即render方法*/}
                <button onClick={() => {
                     this.setState({
            	 		count: this.state.count+1
           			  })   
                }}>点我+1</button>
            </div>
        )
    }
}

3.3 从JSX中抽离事件处理程序

JSX中参杂过多JS逻辑代码会显得非常混乱,从中抽出可保证JSX结构清晰。

  • 需注意: 抽离时this的绑定,直接抽离this会是undefined类型,即以下代码发生报错:TypeError: Cannot red property ‘setState’ of undefined

  • 原因: 必须谨慎对待 JSX 回调函数中的 this,类的方法默认是不会绑定 this 的,当你调用这个函数的时候 this 的值会是 undefined

// 会报错的抽离方法
class Hello extends React.Component {
    // 初始化事件
    state = {
        count: 0
    }
	// 事件处理程序
    onIncrement(){
        this.setState({
        	count: this.state.count + 1
        })
    }
	render(){
        return(
            <div>
                <div>计数器:{this.state.count}</div>
                {/* 这里的this无绑定,为undefined*/}
                <button onClick={this.onIncrement}>点我+1</button>  
            </div>
        )
	}
}

解决方案有三种:

1.箭头函数:

利用箭头函数自身不绑定的特点,render方法中的this为组件实例,可以获取到setState()

class Hello extends React.Component {
    // 初始化事件
    state = {
        count: 0
    }
	// 事件处理程序
    onIncrement(){
        this.setState({
        	count: this.state.count + 1
        })
    }
	render(){
        return(
            <div>
                <div>计数器:{this.state.count}</div>
                <button onClick={() => this.onIncrement()}>点我+1</button>  
            </div>
        )
	}
}

2.Function.prototype.bind():

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

class Hello extends React.Component {
    construct() {
        super()
        // Function.prototype.bind()绑定this
        this.onIncrement = this.onIncrement.bind(this)
	}
    // 初始化事件
    state = {
        count: 0
    }
	// 事件处理程序
    onIncrement(){
        this.setState({
        	count: this.state.count + 1
        })
    }
	render(){
        return(
            <div>
                <div>计数器:{this.state.count}</div>
                <button onClick={this.onIncrement}>点我+1</button>  
            </div>
        )
	}
}

3.class的实例方法(推荐):

利用箭头函数形式的class实例方法解决this指向问题,该语法是实验性语法,但由于babel的存在可以使用

class Hello extends React.Component {
    // 初始化事件
    state = {
        count: 0
    }
	// 事件处理程序,改造为箭头函数方法
    onIncrement = () => {
        this.setState({
        	count: this.state.count + 1
        })
    }
	render(){
        return(
            <div>
                <div>计数器:{this.state.count}</div>
                {/* 这里的this无绑定,为undefined*/}
                <button onClick={this.onIncrement}>点我+1</button>  
            </div>
        )
	}
}

4、表单处理

4.1受控组件

受控组件即值受react控制的表单元素。HTML中的表单元素是可输入的,也就是有自己的可变状态,React将state与表单元素值value绑定在一起,由state的值来控制表单元素的值

使用步骤:

  • 在state中添加一个状态,作为表单元素的value值
  • 给表单元素绑定change事件,将表单元素的值设置为state的值
class App extends React.Component {
    constructor(){
        super()
        this.inputChange = this.inputChange.bind(this)
    }
    state = {
        txt : ''
    }
    inputChange(e){
       this.setState({
           txt: e.target.value
       })
    }
    render(){
        console.log(this.state);
        
        return (
            <div>
                {/* 把state的值设置给输入框的value,绑定change事件,从而达到数据的统一 */}
                <input type="text" value={this.state.txt} onChange={this.inputChange}/>
            </div> 
        )
    }
}
ReactDOM.render(<App />,document.getElementById('root'))

多表单元素优化:

每个表单元素都有一个单独的事件处理函数,这样太繁琐,可以使用一个事件处理程序同时处理多个表单元素

使用步骤:

  • 给表单元素添加name属性(用来区分是哪一个表单),名称与state相同(用来更新数据的)
  • 根据表单内容来获取对应值
  • 在change事件处理程序中通过 [name](结构,将变量用用作属性名)来修改对应的state
inputChange(e){
   let target = e.target;
   let value = target.type == 'checkbox' ? target.checked : target.value;
   this.setState({
       [e.target.name]: value
   })
}
<input type="text" value={this.state.txt} name="txt" onChange={this.inputChange}/>
<input type="checkbox" value={this.state.isChecked} name="isChecked" onChange={this.inputChange}/>

4.2 非受控组件

非受控组件借助于ref,使用原生DOM的方式来获取表单元素的值,ref在其中起到获取DOM或组件的作用(直接操作DOM,不推荐

使用步骤:

  • 调用 React.createRef() 方法创建ref对象
constructor() {
    super()
    this.txtRef = React.createRef()
}
  • 将创建好的 ref 对象添加到文本框中,实现关联
<input type="text" ref={this.txtRef}></input>
  • 通过ref对象获取到文本框的值
Consle.log(this.txtRef.current.value)

四、组件通讯

组件是独立且封闭的单元,默认情况下,只能使用组件自己的数据。在组件化过程中,我们将一个完整的功能拆分成多个组件,以更好的完成整个应用的功能。而在这个过程中,多个组件之间不可避免的要共享某些数据。为了实现这些功能,就需要打破组件的独立封闭性,让其与外界沟通,这个过程就是组件通讯

1、组件的props

1.1 组件如何接收数据

  • 组件是封闭的,要接收外部数据应该通过props来实现
  • 函数组件通过参数props接收数据,类组件通过this.props接收数据
  • 传递数据的方式是给组件标签添加属性

传递数据:

<Hello name="jack" age={21} />

函数组件接收数据:

function Hello(props) {
    congsole.log(props)
    return (
        <div>接收到的数据为:{props.name}</div>
    )
}

类组件接收数据:

class Hello extends React.Component {
    render() {
        return (
        	<div>接收到的数据为:{this.props.age}</div>
        )
    }
}

1.2 props的特点

  • 传递的数据可以是任意类型(字符串、数组、函数、数值、JSX、对象等),字符串使用""其他类型使用{}
  • props是只读属性,不能对值进行修改
  • 使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到props,其他的地方是可以拿到

含构造函数的类组件使用props:

class Hello extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
        	<div>接收到的数据为:{this.props.age}</div>
        )
    }    
}

2、组件通讯的几种方式

2.1 父组件传递数据给子组件

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

父组件传递数据给子组件:

class Parent extends React.Component {
    state = {
        lastName: hao
    }
	render() {
        return (
            <div>
                传递数据给子组件:
                <Child name={this.state.lastName}/>
            </div>
        )
	}
}

子组件接收数据:

function Child(props) {
	return <div>子组接收数据:{props.name}/></div>
}

2.2 子组件传递数据给父组件:

  • 利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数
  • 父组件提供一个回调函数,用来接收数据
  • 将该函数作为属性的值,传递给子组件

父组件提供回调函数:

class Parent extends React.Component {
	getChildMsg = (msg) => {
        console.log('接收到子组件的数据', msg)
	}
	render() {
        return (
            <div>
                子组件:
                <Child getMsg={this.getChildMsg}/>
            </div>
        )
	}
}

子组件通过props调用回调函数:

class Child extends React.Component {
	state = { childMsg: 'React' }
	handleClick = () => {
		this.props.getMsg(this.state.childMsg)
    }
    return (
    	<button onClick={this.handleClick}>点我给父组件传递数据</button>
    )
}

2.3 兄弟组件之间的通讯

  • 将共享状态(数据)提升到最近的公共父组件中,由公共父组件管理这个状态,即状态提升
  • 公共父组件职责: 1. 提供共享状态 2.提供操作共享状态的方法
  • 要通讯的子组件只需要通过props接收状态或操作状态的方法

demo:

  • 定义布局结构,一个Counter里面包含两个子组件,一个是计数器的提示,一个是按钮
class Counter extends React.Component {
    render() {
        return (<div>
            <Child1 />
            <Child2 />
        </div>
        )
    }
}
class Child1 extends React.Component {
    render() {
        return (
            <h1>计数器:</h1>
        )
    }
}
class Child2 extends React.Component {
    render() {
        return (
            <button>+1</button>
        )
    }
}
  • 在父组件里定义共享状态,把这个状态传递给第一个子组件
class Counter extends React.Component {
    // 提供共享的状态
    state = {
        count: 0
    }
    render() {
        return (<div>
            {/* 把状态提供给第一个子组件 */}
            <Child1 count={this.state.count}/>
            <Child2 />
        </div>
        )
    }
}
  • 在第一个子组件里面通过props获取数据
class Child1 extends React.Component {
    render() {
        return (
            <h1>计数器:{this.props.count}</h1>
        )
    }
}
  • 父组件中提供共享方法,通过属性传递给第二个子组件,方便第二个子组件来进行调用
// 提供共享方法
onIncrement = (res) => {
    // 只要第二个子组件调用了这个函数,就会执行里面代码
    this.setState({
        count: this.state.count + res
    })
}
render() {
    return (<div>
            ...
            {/* 把共享方法提供给第二个子组件 */}
            <Child2 onIncrement={this.onIncrement} />
        </div>
    )
}
  • 在第二个子组件里面通过props来获取到对应函数,然后进行调用
class Child2 extends React.Component {
    handleClick = () => {
        // 这里一旦调用,就会执行父组件里面 onIncrement函数
        this.props.onIncrement(2)
    }
    render() {
        return (
            <button onClick={this.handleClick}>+</button>
        )
    }
}

2.4 Context

如果出现层级比较多的情况下(例如:爷爷传递数据给孙子),我们会使用Context来进行传递,其作用是跨组件传递数据

步骤:

  • 调用 React.createContext()创建 Provider(提供数据)和 Consumer(消费数据) 两个组件
const {Provider, Consumer} = React.createContext()
  • 使用Provider 组件作为父节点,并设置value属性,表示要传递的数据
<Provider value="pink">
    <div className="App">
        <Child1 />
    </div>
</Provider>
  • 哪一层想要接收数据,就用Consumer进行包裹,在里面回调函数中的参数就是传递过来的值
<Consumer>
    {data => <span>data参数表示接收到的数据:{data}</span>}
</Consumer>

3、props进阶

3.1 children属性

  • children属性: 表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
  • children属性与普通的props一样,值可以是任意值(文本、react元素、组件、甚至是函数)
function Hello(props) {
    return (
    	<div>
            组件的子节点:{props.chidren}
        </div>
    )
}
// "我是子节点"就是Hello的子节点
<Hello>我是子节点</Hello>

3.2 props校验

对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道封装的组件需要什么样的数据,如果传入的数据不对,可能会导致报错,这个时候就需要用到props校验了

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

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

使用步骤:

  • 安装prop-types包:npm i props-types
  • 导入prop-types 包
  • 使用组件名.propTypes={} 来给组件的props添加校验规则
  • 校验规则通过PropTypes对象来指定
import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'

function App(props) {
    return (
        <h1>Hi, {props.colors}</h1>
    )
}

App.propTypes = {
    colors: PropTypes.array
}

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

常见约束规则:

  • 常见类型:array、bool、number、string、object、func
  • React元素类型(element)
  • 必填项:isrequired
  • 特定结构的对象
App.propTypes = {  
	// 常见类型
    optionalFunc: PropTypes.func,
    // 必选项
    requiredFunc: PropTypes.func.isRequired,
    // 特定结构的对象
    optionalObjectWithShape: PropTypes.shape({
		color: PropTypes.string,
        fontSize: PropTypes.number
	})
}

3.3 props的默认值

  • 场景:分页组件 -> 每页显示条数
function App(props) {
    return (
        <div>
            props默认值:{props.pageSize}
        </div>
    )
}
//设置默认值
App.defaultProps = {
    pageSize: 10
}
// 不传入pageSize属性
ReactDOM.render(<App />, document.getElementById('root'))

五、组件的生命周期

组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程就是组件的生命周期。生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数钩子函数为开发人员在不同阶段操作组件提供了时机。掌握组件的生命周期有助于理解组件的运行方式,完成更复杂的组件功能、分析组件错误原因等。只有类组件才有生命周期

1、组件生命周期的三个阶段

生命周期

1.1 创建时(挂载阶段)

  • 执行时机:组件创建时(页面加载时)
  • 执行顺序:constructorrendercomponentDidMount
钩子函数触发时机作用
constructor创建组件时,最先执行1.初始化state;2.为事件处理程序绑定this
render每次组件渲染都会触发渲染UI( 注意: 不能调用setState() ,无限递归)
componentDidMount组件挂载(完成DOM渲染)后1.发送网络请求;2.DOM操作

1.2 更新时

  • 执行时机:setState()、 forceUpdate()、 组件接收到新的props,以上三者任意一种变化,组件就会重新渲染
  • 执行顺序:rendercomponentDidMount
钩子函数触发时机作用
render每次组件渲染都会触发渲染UI( 与挂载阶段是同一个render)
componentDidMount组件挂载(完成DOM渲染)后1.发送网络请求;2.DOM操作;(注意: 如果要setState()必须放在应该条件判断中,避免无限递归,可判断props是否相同)

1.3 卸载时

  • 执行时机:组件从页面中消失
  • 作用:用来做清理操作
钩子函数触发时机作用
compontentWillUnmount组件卸载(从页面中消失)执行清理工作(如清理定时器)

1.4 不常用的钩子函数

不常用钩子函数

五、render-props和React高阶组件

如果两个组件中的部分功能相似或相同,如何复用相似的功能?需要复用哪些?由哪些方式?我们可以通过render props模式高阶组件(HOC)复用state操作state的方法实现功能复用。这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式

1、render-props模式

  • 如何拿到该组件中复用的state: 在使用组件时,添加一个值为函数的prop,通过函数参数来获取

  • 如何渲染到任意UI: 使用该函数的返回值作为要渲染的UI内容

使用步骤:

  • 创建Mouse组件,在组件中提供复用的逻辑代码

  • 将要复用的状态作为 props.render(state)方法的参数,暴露到组件外部(注意: 并不是该模式叫 render props就必须使用名为render的prop,实际上可以使用任意名称的prop,我们把prop是一个函数并且告诉组件要渲染什么内容的技术叫做render props模式,推荐使用children代替render属性,更加直观)

  • 使用props.render() 的返回值作为要渲染的内容

// 创建Mouse组件,提供复用的逻辑代码
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() {
        // 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部
        // 这里的render是props参数的render,把state暴露了出去
        // 这里推荐用children
        return this.props.render_children(this.state)
    }
}
class App2 extends React.Component {
    render() {
        return (
            <div>
                <h1> render props模式</h1>
                {/* 使用props.render() 的返回值作为要渲染的内容,这里的render就是上面props。render() */}
                {/* 推荐用children,那么这里就可以不用写"render_children=“ */}
                <Mouse render_children={(mouse) => {
                    return (
                        <p>
                            鼠标位置:{mouse.x} {mouse.y}
                        </p>
                    )
                }} />
                {/* 鼠标小天使图片跟随,再次复用 */}
                {/* 推荐用children,那么这里就可以不用写"render_children=“ */}
                <Mouse render_children={mouse => {
                    return <img src={img} alt="小天使" style={{
                        position: 'absolute',
                        top: mouse.y - 40,
                        left: mouse.x - 40
                    }} />
                }} />
            </div>
        )
    }
}
ReactDOM.render(<App2 />, document.getElementById('root'))

// TODO: 1.添加props校验 2.组件卸载时移除mousemove事件绑定

2、高阶组件HOC

  • 高阶组件HOC是一个函数,接收要包装的组件,返回增强后的组件,它通过包装(装饰)模式实现组件的复用。

  • 原理: 高阶组件内部创建了一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent

使用步骤

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

包装函数

// 定义一个函数,在函数内部创建一个相应类组件
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() {
            // 在render函数里面返回传递过来的组件,把当前组件的状态设置进去
            return <WrappedComponent {...this.state} />
        }
    }
    return Mouse
}

哪个组件需要加强,通过调用withMouse这个函数,然后把返回的值设置到父组件中即可

const Position = props => (
    <p>
        鼠标当前位置:(x: {props.x},y:{props.y}</p>
)
const Angel = props => (
    <img src={img} alt="小天使" style={{
        position: 'absolute',
        top: props.y - 40,
        left: props.x - 40
    }} />
)
// 获取增强后的组件
const MousePosition = withMouse(Position)
const MouseAngel = withMouse(Angel)

class App extends React.Component {
    render() {
        return (
            <div>
                <h1>高阶组件</h1>
                {/* 渲染组件 */}
                <MousePosition />
                <MouseAngel />
            </div>
        )
    }
}
ReactDOM.render(<App />, document.getElementById('root'))

tips:设置displayName

displayName用于设置调试信息(React Developer Tools信息。默认情况下,React使用组件名称作为displayName,那么通过高阶组件构造的两个组件的名称就是相同的,如上在调试时,会显示由两个Mouse组件。为高阶组件设置displayName便于在调试时区分不同的组件

// 写在组件内
Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`

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

传递props

如果没有传递props,会导致props((组件的属性))丢失问题, 渲染WrappedComponent时,将state和props一起传递给组件

 <WrappedComponent {...this.state} {...this.props} />

六、React原理

1、setState()

  • setState()更新数据是异步的,那么使用该语法,后面的setState不要依赖前面setState的值,多次调用setState,只会触发一次render

  • 推荐语法: setState((state,props) => {}),参数state:表示最新的state,参数props表示最新的props,同样是异步的

setState()更新数据是异步的

handleClick = () => {
    this.setState({
        count: this.state.count + 1 // 1 + 1
    })
    this.setState({
        count: this.state.count + 1 // 1 + 1
    })
    console.log('count:', this.state.count) //1
}

使用推荐语法后的效果,虽然同样是异步更新的,该方法的不同点是setState的参数是回调函数,后边是基于前边的

handleClick = () => {
    this.setState((state, props) => {
		return {
			count: this.state.count + 1 // 1 + 1
        }
    })
    this.setState((state, props) => {
		return {
            console.log(state.count) // 2,已拿到更新后的结果
			count: this.state.count + 1 // 1 + 1
        }
    })
    console.log('count:', this.state.count) // 1
}
  • setState()的第二个参数

setState(update, [callback]),在状态更新(页面完成重新渲染)后立即执行某个操作,可以操作DOM

2、JSX语法的转化过程

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

JSX语法转化过程

3、组件更新机制及性能优化

3.1 组件更新机制

我们都知道setState()的两个作用分别是修改state和更新组件,整个过程中,父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子孙组件)

3.2 组件性能优化

  • 减轻state: 只存储跟组件渲染相关的数据(比如:count/ 列表数据 /loading等),不用做渲染的数据不要放在state中,对于这种需要在多个方法中用到的数据,应该放到this中

  • 避免不必要的重新渲染: 父组件更新会引起子组件也被更新,问题是子组件没有任何变化时也会重新渲染,通过使用钩子函数shouldComponentUpdate(nextProps, nextState),根据条件决定是否程序渲染组件

    • nextPropsnextState是最新的状态以及属性,自身的用state,接收到的用props
    • 通过返回值判断是否需要重新渲染,返回值为true代表需要重新渲染,返回值为false代表不需要重新渲染
    • 这个钩子函数的触发事件为更新阶段,组件重新渲染前执行(shouldComponentUpdate → render)
  • 纯组件: React.PureComponent 与 React.Component 功能相似,但PureComponent 内部自动实现了 shouldComponentUpdate钩子函数,不需要手动比较。其内部通过分别比对前后两次 props和state的值,来决定是否重新渲染组件

减轻state:

class Hello extends Component {
	componentDidMount() {
        // timerId存储到this中,而不是放在state中
        this.timerId = setInterval(() => {}, 2000)
    }
    componentWillUnmount() {
        clearInterval(this.timerId)
    }
    render() {...}
}

避免不必要的重新渲染:

class Hello extends Component {
    shouldComponentUpdate() {
        // 此处根据条件决定是否重新渲染,返回true或false
        return false 
    }
    
    render() {...}
}

纯组件:

class Hello extends React.PureComponent {
    render() {
        return (
            <div>纯组件</div>
        )
    }
}

纯组件实现原理:

纯组件内部的对比是shallow compare(浅层对比),对于值类型来说比较两个值是否相同,对于引用类型来说只比对对象的引用地址是否相同。因此要注意:state 或 props 中属性值为引用类型时,应该创建新数据不要直接修改原数据

4、 虚拟DOM和Diff算法

React更新视图的思想是只要state变化就重新渲染视图,但组件中只有一个DOM元素需要更新时,也得把整个组件的内容重新渲染的话显然效率不高,为了提高效率只更新变化的地方才是理想的,在React中就是运用虚拟DOM 配合Diff 算法完成的

1、虚拟DOM

本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容
虚拟DOM

2、Diff算法

执行过程

  • 初次渲染时,React会根据初始化的state(model),创建一个虚拟DOM对象(树)
  • 根据虚拟DOM生成真正的DOM,渲染到页面
  • 当数据变化后(setState()),会重新根据新的数据,创建新的虚拟DOM对象(树)
  • 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
  • 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面

Diff算法

*详细算法解析参见博文

七、React路由基础

现代的前端应用大多数是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力更小,所以更受欢迎。为了有效的使用单个页面来管理多页面的功能,前端路由应运而生。前端路由的作用就是让用户从一个视图(页面)导航到另一个视图(页面)。它是一套映射规则,在React中,是URL路径与组件的对应关系,使用React路由简单来说,就是配置路径和组件

1、路由的基本使用

1.1 安装React路由

  • npm install react-router --save 安装路由
  • npm install react-router-dom --save 安装路由DOM

1.2 使用步骤

  • 导入路由的三个核心组件: Router/Route/Link
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
  • 使用Router组件包裹整个应用
const App = () => (
    <Router>
        <div>
            <h1>React 路由</h1>
        </div>
    </Router>
)
  • 使用Link组件作为导航菜单(路由入口
 <Link to="/first">页面一</Link>
  • 使用Route组件配置路由规则和要展示的组件(路由出口)
const First = () => <p>我是页面一</p>
const App = () => (
    <Router>
        <div>
            <h1>React 路由</h1>
        </div>
        {/* 指定路由入口 */}
        <Link to="/first">页面一</Link>
        {/* 指定路由出口 */}
        <Route path="/first" component={First} />
    </Router>
)
ReactDOM.render(<App />, document.getElementById('root'))

1.3常用组件说明

  • Router组件: 包裹整个应用,一个React应用只需要使用一次
    • 两种常用的Router: HashRouter和BrowserRouter
    • HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first),带#不推荐使用
    • BrowserRouter(推荐): 使用H5的history API实现(localhost3000/first)
  • Link组件: 用于指定导航链接(a标签)
    • 最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
  • Route组件: 指定路由展示组件相关信息
    • path属性: 路由规则,这里需要跟Link组件里面to属性的值一致
    • component属性: 展示的组件
    • 组件位置: Route写在哪,渲染出来的组件就在哪

1.4 路由指向过程

  • 当我们点击Link组件的时候,修改了浏览器地址栏中的url
  • React路由监听地址栏url的变化
  • React路由内部遍历所有的Route组件,拿着Route里面path规则与url的pathname进行匹配

1.5. 默认路由

  • 默认路由: 表示进入页面时就会匹配的路由
  • 默认路由:只需要把path设置为 '/'

2、编程式导航

  • 场景: 点击登陆按钮,登陆成功后,通过代码跳转到后台首页,如何实现?
  • 编程式导航: 通过JS代码来实现页面跳转
  • history是React路由提供的,用于获取浏览器历史记录的相关信息
  • push(path): 跳转到某个页面,参数path表示要跳转的路径
  • go(n):前进或后退功能,参数n表示前进或后退页面数量
class Login extends Component {
    handleLogin => () {
        this props.history.push('/home')
    }
	render() {...}
}

3、匹配模式

3.1 模糊匹配模式

  • 当Link组件的to属性值为 '/login'时候,为什么默认路由也被匹配成功?
  • 默认情况下,React路由是模糊匹配模式
  • 模糊匹配规则:只要pathname以path开头就会匹配成功

3.2 精准匹配

  • 默认路由认可情况下都会展示,如果避免这种问题?
  • 给Route组件添加exact属性,让其变为精准匹配模式
  • 精确匹配:只有当path和pathname完全匹配时才会展示改路由
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-02 11:15:56  更:2021-09-02 11:18:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 21:12:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计