通俗易懂,入门React基础笔记

react

react的基本使用

  • 初始化项目: npx creact-react-app hkzf-mobile
  • 启动项目:在项目目录执行命令:yarn start

React的安装

安装命令:npm i react react-dom (同时安装两个包)

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

React的使用

  1. 引入react和react-dom两个js文件(注意引入顺序,先是react后是react-dom)

    <script src="./node_modules/react/umd/react.development.js"></script>
    <script src="./node_modules/react/umd/react-dom.development.js"></script>
    
  2. 创建React元素(有三个参数)

    React.createElment()

    • 参数一:元素名称
    • 参数二:元素属性
    • 参数三及其以后的参数:元素的子节点
  3. 渲染React元素到页面中

    ReactDOM.render()

    • 参数一:要渲染的react元素
    • 参数二:挂载点
    //注意大小写问题
    <div id="root"></div>
    <script>
      // 这里的React由react包提供
      conse title = React.createElement('h1',null,'Hello React')
    //这里的ReactDom由react-dom包提供
    	ReactDOM.render(title,document.getElementById('root'))  
     </script>
    
  4. null

react脚手架的基本使用

使用React脚手架初始化项目

  1. 初始化项目,命令:npx create-react-app my-app //create-react-app //脚手架的名称 my-app是项目名称

  2. 启动项目,在项目根目录执行命令:npm start

npx命令介绍

  • npm v5.2.0 引入的一条命令
  • 目的:提升包内提供的命令行工具的使用体验
  • 原来:先安装脚手架包,再使用这个包中提供的命令
  • 现在:无需安装脚手架包,就可以直接使用这个包提供的命令

初始化项目

  • 推荐使用:npx create-react-app my-app
  • npm init react-app my-app
  • yarn creact react-app my-app

react脚手架中使用react

  1. 导入react和react-dom两个包

    import React from 'reacr'
    import ReactDOM from 'react-dom'
    
  2. 调用React.createElment()方法创建react元素

  3. 调用ReactDOM.render()方法渲染react元素到页面中 //开发的是web应用,不同的应用导入不同的渲染函数

react基础阶段总结

JSX

JSX的基本使用

  1. createElement()的问题
    • 繁琐不简洁
    • 不直观,无法一眼看出所描述的结构
    • 不优雅,用户体验不爽
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9rOSgjI-1642920168810)(D:\资料\web笔记\react\image\笔记\image-20211217153347234.png)]
  2. JSX简介
    • JSX是JavaScript XML的简写,表示在JavaSript代码中写XML(HTML)格式的代码
    • 优势:声明式语法更加直观、与HTML结构相同,降低了学习成本、提升开发的效率
  3. JSX是React的核心内容
  4. 使用JSX步骤
    1. 使用JSX语法创建react元素
      • const title =

        Hello JSX

    2. 使用ReactDOM.render()方法渲染react元素到页面中
      • 渲染创建好的React元素
      • ReactDOM.render(title,root)

为什么脚手架中要使用JSX语法

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

JSX的注意点

  • React元素的属性名使用驼峰命名法

  • 特殊属性名:class->className、 for->htmlFor、 tabindex->tabIndex

  • 没有子节点的React元素可以用 /> 结束

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

    • //使用小括号包裹JSX
      const dv = (
      	<div>hello JSX</div>
      )
      

在JSX中使用JavaScript表达式

  1. 嵌入JS表达式

    • 数据存储在JS中

    • 语法:{JavaScript表达式}

    • 注意:语法是单大括号,不是双大括号

    • const name = 'hauge'
      const dv = (
      	<div>你好,我叫{name}</div>
      )
      

JSX的条件渲染

  • 场景:loading效果
  • 条件渲染:根据条件渲染特定的JSX结构
  • 可以使用if/else三元运算符逻辑运算符来实现
  1. import React from 'react'
    import ReactDOM form 'react-dom'
    
    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'))
    

JSX的列表渲染

  • 如果要渲染一组数据,应该使用数据的map()方法 //map() 映射

  • 注意:渲染列表是应该添加key属性,key属性的值要保证唯一

  • 原则:map()遍历创建什么元素,就给什么元素添加key属性

  • 注意:尽量避免使用索引号作为key

  • null

  • const songs = [
      {id:1,name:'li'},
      {id:2,name:'hua'},
      {id:3,name:'ge'}
    ]
    const list = (
    	<ul>
      	{songs.map(item=><li key={item.id}>{item.name}</li>)}
      </ul>
    )
    

JSX的样式处理

  • 行内样式 —style

    第一个花括号是使用表达式
    第二个花括号是一个对象
    <h1 style={{color:'res',backgroundColor:'skyblue'}}>
     	JSX的样式处理
    </h1>
    
  • 类名–className(推荐)

    <h1 className = "title">
     	JSX的样式处理
    </h1>
    

JSX阶段性总结

  • React完全利用JS语言自身的能力来编写UI,而不是造轮子增强HTML功能,React没有指令一说,就连渲染列表都是使用map()

React组件基础学习

  • 能够使用函数创建组件
  • 能够使用class创建组件
  • 能够给React元素绑定事件
  • 能够使用state和setState()
  • 能够处理事件中的this指向问题
  • 能够使用受控组件方式处理表单

React组件介绍

  • 组件是React的一等公民,使用React就是用组件
  • 组件表示页面中的部分功能
  • 组合多个组件实现完整的页面功能
  • 特点:可复用、独立、可组合

组件的两种创建方式(1函数组件)

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

  • 约定1:函数名称必须以大写字母开头

  • 约定2:函数组件必须有返回值,表示该组件的结构

  • 如果返回值为null,表示不渲染任何内容 // 如果不想返回任何东西,就return null

function Hello () {
  return (
  	<div>函数创建组件</div>
  )
}
  • 渲染函数组件:用函数名作为组件标签名

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

组件的两种创建方式(2类创建组件)

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

  • 约定1:类名称也必须以大写字母开头

  • 约定2:类组件应该继承React.Component父类,从而可以使用父类中提供的方法或属性

  • 约定3:类组件必须提供==render()==方法

  • 约定4:render()方法必须有返回值,表示该组件的结构

  • class Hello extends React.Component {
      render(){
        return <div> 类创建组件 </div>
      }
    }
    ReactDOM.render(<Hello />, document.getElmentById('root'))
    

组件的两种创建方式(3抽离为独立js文件)

  1. 步骤

    1. 创建Hello.js
    2. 在Hello.js中导入React
    3. 创建组件(函数创建或者类创建)
    4. 在Hello.js中导出该组件
    5. 在index.js中导入Hello组件
    6. 渲染组件
  2. // Hello.js   组件
    //导入React
    import React from 'react'
    class Hello extends React.Component {
      render(){
        return (R
        	<div>Hello 组件</div>
        )
      }
    }
    // 导出该组件
    export default Hello
    
    
    // index.js
    // 导入Hello组件
    import Hello from './Hello'
    // 渲染导入的Hello组件
    ReactDOM.render(<Hello />,root)
    
    

React事件处理(事件绑定)

事件绑定1

  • React事件绑定语法与DOM事件语法相似

  • 语法:on+事件名称 = {事件处理程序},比如:onClick={() => {}}

  • 注意:React事件采用驼峰命名法,比如:onMouseEnter、onFocus

  • //类组件绑定事件
    class App extends React.Component {
      handleClick() {
        console.log('单击事件触发了')
      }
      render() {
        return (
        	<button onClick={this.handleClick}></button>
        )
      }
    }
    
  • // 函数组件绑定事件 (没有this)
    function App(){
      function handleClick() {
        consle.log('函数组件绑定事件')
      }
      return (
      	<button onClick={handleClick}></button>
      )
    }
    

事件对象

  • 可以通过事件处理程序的参数获取到事件对象

  • React中的事件对象叫做:合成事件(对象)

  • 合成事件:兼容所有的浏览器,无需担心跨浏览器兼容性问题

  • function handleClick(e) {
      e.preventDefault() // 阻止点击a标签自动跳转 /阻止默认浏览器行为
      console.log('事件对象',e)
    }
    <a onClick = {handleClick}> 点我,不会跳转页面</a>
    

有状态组件和无状态组件

  • 函数组件又叫无状态组件类组件又叫做有状态组件
  • 状态(state)即数据
  • 函数组件没有自己的状态,只负责数据显示(静)
  • 类组件有自己的状态,负责更新UI,页面“动”起来// 比如计数器增加页面显示,此功能就要使用有状态组件来完成

state和setState()

state的基本使用

  • 状态(state)即数据,

  • 组件内部的私有数据,只能在组件内部使用

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

  • 通过this.state来获取状态 b

  • class Hello extends React.Component {
      constructor(){
        super()
        //初始化state
        this.state = {
          count:0
        }
      }
      rende() {
        return (
        	<div>有状态组件{this.state.count}</div>
        )
      }
    }
    
    // 简化写法
    
    class Hello extends React.Component {
      //初始化state
        state = {
          count:0
        }
      rende() {
        return (
        	<div>有状态组件{this.state.count}</div>
        )
      }
    }
    

setState()修改状态

  • 状态是可变的

  • 语法:this.setState({要修改的数据})

  • 注意:不要直接修改state中的值,这是错误的!!!

  • setState()作用:1.修改state,2.更新UI

  • 思想:数据驱动视图

  • // 正确的
    this.setState({
      count:this.state.count+1
    })
    //错误的
    this.state.count += 1
    

组件中的state和setState() (2从JSX中抽离事件处理程序)

class Hello extends React.Component {
  state = {
      count:0
    }
//事件处理程序
onIncrement(){
  console.log('事件处理程序的this'this) // this为undefind
  //报错:TypeError:Cannot read property 'setState' of undefind,因为this为undefind
  this.setState({
    count:this.state.cound + 1
  })
}
  render() {
    return (
    	//<button onClick={this.onIncrement}>点击+1</button>
      //这里之所以没有报错是因为使用了箭头函数,箭头函数本身没this的,这里的this会往外部环境找,也就是render方法,render里面的this是可以获取到setState的即也就是组件实例
      <button onClick={()=>{
        this.setState({
          count:this.state.cound + 1
        })
      }}>点击+1</button>
    )
  }
}
  • JSX中掺杂过多JS逻辑代码,会显得非常的混乱
  • 推荐:将逻辑抽离到单独的方法中,保证JSX结构清晰
  • 原因:事件处理程序中this的值为undefined
  • 希望:this指向组件实例(render方法中的this即为组件实例)
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Dik2EnMK-1642920168815)(D:\资料\web笔记\react\image\笔记\image-20211220181534485.png)]
  • 注意:TypeError:Cannot read property ‘setState’ of undefind //无法读取undefind的属性“setState”
    • setState是this读取的也就是this出问题了,tihs为undefind
  • 箭头函数自身是没有this的,
  • null

事件绑定this指向

事件绑定this指向(1箭头函数)

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

  • render()方法中的this为组件实例,可以获取setState()

  • 箭头函数里面的this是由外部环境决定的、外部环境的this是谁,箭头函数的this就是谁

  • class Hello extends React.Component {
      state = {
          count:0
        }
    //事件处理程序
    onIncrement(){
    //这里this是谁调用指向谁,(因为是箭头函数调用)此处是render方法里面的this调用的,指向render也就是组件实例,所以是可以拿到setState的
      this.setState({
        count:this.state.cound + 1
      })
    }
      render() {
        return (
        //箭头函数中的this指向外部环境,此处为:render()方法的this
          <button onClick={()=>{this.onIncrement()}>点击+1</button>
        )
      }
    }
    

事件绑定this指向(2bind)

  • Function.prototype.bind()

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

  • class Hello extends React.Component {
      
      //constructor里面的this也是指向实例的
     constructor(){
       super()
        state = {
          count:0
        }
       this.onIncrement = this.onIncrement.bind(this)
     }
    //事件处理程序
    onIncrement(){
      this.setState({
        count:this.state.cound + 1
      })
    }
      render() {
        return (
          <button onClick={this.onIncrement}>点击+1</button>
        )
      }
    }
    

事件绑定this指向(3class的实例方法)

  • 利用箭头函数形式的class实例方法

  • 注意:改语法是实验性语法,但是,由于babel的存在可以直接使用

  • class Hello extends React.Component {
      
       state = {
          count:0
        }
    //事件处理程序
    //这里自身就是一个箭头函数,所以这里的this会执行实例的
    onIncrement = () => {
      this.setState({
        count:this.state.cound + 1
      })
    }
      render() {
        return (
          <button onClick={this.onIncrement}>点击+1</button>
        )
      }
    }
    

事件绑定this指向总结

  1. 推荐:使用class的实例方法

    • class Hello extends React.Component {
        onIncrement = () => {
          this.setState({...})
        }
      }
      
  2. 箭头函数

    • <button onClick={()=>this.onIncrement()} />
      
  3. bind

    • constructor(){
        super()
        this.onIncrement = this.onIncrment.bind(this)
      }
      

表单处理

表单处理(1受控组件概念)

  • HTML 中的表单元素是可输入的,也就是有自己的可变状态

  • 而,React中可变状态通常保存在state中,并且只能通过setState()方法来修改

  • React将state与表单元素值value绑定到一起,由state的值来控制表单元素的值

  • 受控组件:其值受到React表单控制元素

  • <input type='text' value={this.state.txt} />
    

表单处理(2受控组件使用步骤)

  1. 在state中添加一个状态,作为表单元素的value值(控制表单元素值的来源)

  2. 给表单元素绑定change事件,将表单元素的值设置为state的值(控制表单元素值的变化)

  3. state = { text: ''}
    
    handelChange = e=> {
    	this.setState({
    		text:e.target.value
    	})
    }
    <input type='text' 
    value={this.state.txt} 
    //onChange={e => this.setState({text:e.target.value})}
    onChange={this.handelChange}
    />
    

表单处理(3受控组件的示例)

  • 文本框、富文本框、下拉框 操作value属性
  • 复选框 操作checked属性

表单处理(4多表单元素优化)

多表单元素优化

问题:每个表单元素都有一个单独的事情处理程序,处理太繁琐了

多表单元素优化步骤

  • 给表单元素添加name属性,名称与state相同

  • 根据表单元素类型获取对应的值 (value/checked)

  • 在change事件处理程序中通过[name]来修改对应的state

  • //根据表单元素类型获取值
    state = {
      txt:'',
      isChecked:false
    }
    const value = target.type === 'checkbox'
    	? target.checked
    	: target.value
    	//若三元运算符看,可使用if
    if(target.type === 'checkbox') {
      const value = target.checked
    } else {
      const value = target.value
    }
    
    // 根据name设置对应的state
    this.setState ({
      [name]:value
    })
    
    <input type="test" 
    	name="txt"
    	value={this.state.txt}
    	onChange={this.hangdleForm}
    >
    // 复选框
    <input type="checkbox" 
    	name="isChecked"
    	checked={this.state.isChecked}
    	onChange={this.hangdleForm}
    >
    

表单处理(5非受控组件)

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

  • ref的作用:获取DOM或组件

    使用步骤

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

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

      • <input type = "text" ref={this.txtRef}>
        
    3. 通过ref对象获取到文本框的值 (别忘了current)

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

React组件基础总结

  • 组件的两种创建方式:函数组件和类组件
  • 无状态(函数)组件,负责静态结构展示
  • 有状态(类)组件,负责更新UI,让页面动起来
  • 绑定事件注意this的指向问题
  • 推荐使用受控组件来处理表单
  • 完全利用JS语言的能力创建组件,这是React的思想

React组件基础综合案例

案例需求分析

渲染评论列表

渲染暂无评论并优化代码

获取评论信息

发表评论-1

发表评论-2边界情况处理

React组件进阶学习

  • 能够使用props接收数据
  • 能够实现父子组件之间的通讯
  • 能够实现兄弟组件之间的通讯
  • 能够给组件添加props校验
  • 能够说出生命周期常用的钩子函数
  • 能够知道高阶组件的作用

组件通讯介绍

组件的props基本使用

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

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

传递数据:给组件标签添加属性

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

  • //函数组件
    //接收数据
    const Hello = props => {
      console.log(props)
      return (
      	<div> 接收到数据:{props.name}</div>
      )
    }
    
    //类组件
    //接收数据
    class Hello extends React.Component {
      render(){
      	return (
      		<div> 接收到数据:{this.props.age}</div>
      	)
      }
    }
    //传递数据
    <Hello name="jack" age={18}>
    

组件的props特点

  • 可以给组件传递任意类型的数据

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

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

  • class Hello extends React.Component {
      constructor(props) {
        //推荐将props传递给父类构造函数
        super(props)
      }
      render() {
        return (
        	<div>接收的数据:{this.props.age}</div>
        )
      }
    }
    

props深入

children属性/类似vue的插槽
  • children属性:表示组件标签的子节点。当标签组件有子节点时,props才有children属性

  • children属性与普通的props一样,值可以是任意值(文本、React元素、组件、甚至是函数)

  • function Hello (props) {
      return (
      	<div>
        	组件的子节点:{props.childen}
    		</div>
      )
    }
    <Hello>我是子节点</Hello>
    
props校验

问题

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

解决方案使用props校验

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

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

  • App.propsType = {
      colos:PropType.array
    }
    
  • 明确的错误提示如下:

    • Warning: Failed prop type: Invalid prop ‘colors’ of type ‘number’ supplied to ‘App’, expected ‘array’
    • 译:不合法的‘colors’值,'colors’值是number类型的,但是期望得到的是Array类型

使用步骤

  • 安装包prop-types (yarn add prop-types/ npm i props-types)

  • 导入prop-types包

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

  • 校验通过PropsTypes对象来指定

  • import PropTypes from 'prop-types'
    function App (props) {
      return (
      	<H1>Hi,{props.colors}</H1>
      )
    }
    //添加props校验
    App.propType = {
      //约定colors属性为array类型
      //如果类型不对,则报出明确错误,便于分析错误原因
      colors: PropType.array
    }
    
props校验-约束规则

约束规则

  • 常见类型:array、bool、func、number、objec、string

  • React元素的类型:element

  • 必填项: isRequired

  • 特定结构的对象: shape({})

  • //常见类型
    optionalFunc:PropTypes.func
    //必填(类型后面加isRequired)
    requiredFunc:PropTypes.func.isRequired
    // 特定结构的对象
    optionalObjectWithShape: PropTypes.shape({
      color: PropTypes.string,
      fontSize:PropTypes.number
    })
    
默认值
  • 使用==组件名.defaultProps = {}==来给组件的props设置默认值

  • App.defaultProps = {
      colors:'pink'
    }
    

组件通讯的三种方式

父组件传递数据给子组件

  • 父组件提供要传递的state数据

  • 给子组件标签添加属性,值为state中的数据

  • 子组件中通过props接收父组件中传递的数据

  • //父组件
    class Parent extends React.Conponent {
      state = {lastName : 'li'}
    	render() {
        return (
        	<div>
          	传递数据给子组件: <Child name={this.state.lastName} />
          </div>
        )
      }
    }
    
  • //子组件
    function Child(props) {
      return (
      	<div>子组件接收数据:{props.name} <div/>
      )
    }
    

子组件传递数据给父组件

思路:利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数

  1. 父组件提供一个回调函数(用于接收数据)

  2. 将该函数作为属性的值,传递给子组件

  3. 子组件通过props调用回调函数

  4. 将子组件的数据作为参数传递给回调函数

  5. 注意:回调中this指向的问题

  6. // 父组件
    class Parent extends React.Component {
      //提供回调函数,用来接收数据
      getChildMsg = (data) => {
        console.log('接收到子组件中传递过来的数据',data)
      }
      render() {
        return (
        	<div>
          	子组件:<Child getMsg = {this.getChildMsg} />
          </div>
        )
      }
    }
    
  7. // 子组件
    class Child extends React.Component {
      state = {childMsg:'React'}
    
      handleClick = () => {
        //子组件调用父组件中传递过来的回调函数 /调用父组件的子组件的getMsg属性函数
     		this.props.getMsg(this.state.childMsg)
      }
      render() {
        return (
        	<button onClick={this.handleClick}>点我,给父组件传递数据</button>
        )
      }
    }
    

兄弟组件通讯

  • 共享状态提升到最近的公共父组件中,由公共父组件管理这个状态

  • 思想:状态提升

  • 公共父组件职责:1.提供共享状态,2.提供操作共享状态的方法

  • 要通讯的子组件只需通过props接收状态或操作状态的方法

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GAru3cQS-1642920168818)(D:\资料\web笔记\react\image\笔记\image-20211221172730338.png)]

  • 然后就是正常的子传父,父传子操作了

  • class Counter extends React.Component {
      //提供共享状态
      state = {
        count:0
      }
      //提供修改状态的方法
      onIncrnment = () => {
    		this.setState({
          count:this.state.count+1
        })
      }
      render(){
        return (
        	<div>
          	<Child1 count= {this.state.count} />
    				<Child2 onIncrement= {this.onIncrnment} />
          </div>
        )
      }  
    }
    
    const Child1 = props => {
      return (
      	<h1>计数器:{props.count}</h1>
      )
    }
    const Child2 = props => {
      return (
      	<button onClick={()=>props.onIncrnment()}>点击+1</button>
      )
    }
    

Contex的基本使用

思考:App组件要传递数据给Child组件,该如何处理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MzTrUIAQ-1642920168821)(D:\资料\web笔记\react\image\笔记\image-20211221180359807.png)]

  • 更好的姿势:使用Context

  • 作用:跨组件传递数据(比如:主题,语言等)

    使用步骤

    1. 调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件。

      • const {Provider,Consumer} = React.createContext()
        
    2. 使用Provider组件作为父节点

      • <Provider>
          <div className='App'>
            <Child1 />
          </div>
        </Provider>
        
    3. 设置value属性,表示要传递的数据

      • <provider value="pink"> 
        
    4. 使用Consume组件接收数据

      • data就是上面value传递过来的数据“pink”
        <Consumer>
          {data => <span>data参数表示接收到的数据--{data}</span>}
        </Consumer>
        
    5. 总结

      1. 如果两个组件是远方亲戚(比如,嵌套多层)可以使用Context实现组件通讯
      2. Context提供了两个组件:Provider和Consumer
      3. Provider组件:用来提供数据
      4. Consumer组件:用来消费数据

组件的生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1XjRhTfk-1642920168823)(D:\资料\web笔记\react\image\笔记\image-20211224152740122.png)]

img

概述

只有类组件才有生命周期

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

组件的生命周期三个阶段

  • 每个阶段的执行时机
  • 每个阶段钩子函数的执行顺序
  • 每个阶段钩子函数的作用
创建时(挂载阶段)
  • 执行时机:组件创建时(页面加载时)

  • 执行顺序

    constructor() -> render() -> componentDidMount()

  • import React from 'react'
    import ReactDom from 'react-doom'
    class App extends React.Component {
      constrctor(props) {
        super(propd)
        console.warn('生命周期钩子函数:constrctor')
      }
      render(){
       // 不能再render里面调用setState,会进入递归更新,因为setState会更新状态也会渲染UI
        //错误写法
       // this.setState({
       //  count:this.state.count+1
        //})
         console.warn('生命周期钩子函数:render')
        return null
      }
      componentDidMount(){
        console.warn('生命周期钩子函数:componentDidMount')
      }
    }
    //constrctor、render、componentDidMount
    
  • 钩子函数触发时机作用
    constructor创建组件时,最先执行1.初始化state。2.为事件处理程序绑定this
    render每次组件渲染都会触发渲染UI(注意:==不能调用setState()==会进入死循环)
    componentDidMount组件挂载(完成DOM渲染)后1.发送网络请求2.DOM操作
更新时(更新阶段)
  • 执行时机:

    1. setState()
    2. forceUpdae() //强制执行更新
    3. 组件接收到新的props
  • 说明:以上三者任意一种变化,组件就会重新渲染

  • 执行顺序:

    • render() -> componentDidUpdate()
  • 钩子函数触发时机作用
    render每次组件渲染都会触发渲染UI(与挂载阶段是同一个render)
    componentDidUpdate组件更新(完成DOM渲染)后1.发送网络请求2.DOM操作3.注意:如果要setState()必须放在一个if条件中
  • //componentDidUpdate参数是上一次的props
    componentDidUpdate(prevProps){
      console.log('上一次的props:',prevProps,'当前的props:',this.props)
      //正确调用 setState()
      // 做法:比较更新前后的props是否相同,来决定是否重新渲染组件
      if(prevProps.count !== this.props.count) {
        this.setState({})
        //发送请求的代码也要写在这里(因为发送请求的话,一般最后都会更新state)
      }
    }
    
卸载时(卸载阶段)
  • 执行时机:组件从页面中消失

    • 钩子函数触发时机作用
      componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)
  • //子组件
    //因为他们都是使用同一个this。所以把timerId交给this
    class Counter entends React.Component {
      componentDidMount() {
        //开启定时器
        this.timerId = setInterval(()=>{
          console.log('定时器正在执行~')
        })
      }
      render(){
        return (
        	<H1> hellog </H1>
        )
      }
      componentWillUnmount(){
        clearInterval(this.timerId)
      }
    }
    

不常用钩子函数介绍

render-props和高阶组件概述

  • 思考:如果两个组件中的部分功能相似或相同,该如何处理
  • 处理方式:复用相似的功能(联想函数封装)
  • 复用什么?:1.state 2.操作state的方法(组件状态的逻辑)
  • 两种方式:1.render props模式 2.高阶组件(HOC)
  • 注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)

props模式(1思路分析)

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

  • 问题1.如何拿到组件中复用的state?

  • 在使用组件时,添加一个值为函数的prop,通过函数参数来获取(需要组件内部实现) //(个人理解类似子传父)

  • 问题2:如何渲染UI

  • 使用该函数的返回值作为要渲染的UI内容(需要组件)

  • <Mouse render={mouse => {}} />
    
  • <Mouse render = {mouse => {
                     <p>鼠标当前的位置 {mouse.x},{mouse.y}</p>
                    }}/>
    

props模式(2使用步骤)

  1. 创建Mouse组件,在组件中提供复用的状态逻辑代码(1.状态 2.操作状态的方法)

  2. 将要复用的状态作为props.render(state)方法的参数,暴露到组件外部

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

  4. class Mouse extends React.Component {
      state = {
        x:'',
        y:''
      }
    	// 鼠标移动事件的处理程序
    handleMouseMove = e => {
      this.setState ({
        x=e.clientX,
        y=e.clientY
      })
    }
    //监听鼠标移动事件
    componentDidMount(){
      window.addEventListener('mousemove',this.handleMouseMove)
    }
      render(){
        //把状态暴露到组件外部
        //这里没有渲染任何结构
        return this.props.render(this.state)
      }
    }
    
  5. //这里的mouse参数就是组件暴露的状态
    // 这里的函数返回值就是要渲染的UI
    <Mouse render={(mouse) => <p>鼠标的位置 {mouse.x},{mouse.y}} />
    
  6. //react  react脚手架导入图片资源
    import img from './image/cat.png'
    <Mouse render={ mouse => {
                   return <img src={img} alt="猫" 
    											style={{
                                 position:'absolut',
                                 top:mouse.y,
                                 left:mouse.x
                                }}
    											/>
                  }} />
    
  7. null

props模式(3演示Mouse组件的复用)

  • Mouse组件负责:封装复用的状态逻辑代码(1.状态 2.操作状态的方法)
  • 状态:鼠标坐标(x,y)
  • 操作状态的方法:鼠标移动事件
  • 传入的render prop负责:使用复用的状态来渲染UI结构

props模式(4children代替render属性)

用children更清晰

  • 注意:并不是该模式叫render props就必须使用名为 render的prop,实际上可以使用任意名称的prop

  • 把prop是一个函数并且告诉组件要渲染什么内容的技术叫做:render props模式

  • 推荐:使用children代替render属性

    • //使用render
      <Mouse render={(mouse) => <p>鼠标的位置 {mouse.x},{mouse.y}} />
      
      
      //使用children
      <Mouse>
        {({x,y}) => <p>鼠标的位置是{x}{y}</p>}
      </Mouse>
      
      // 组件内部:
      this.props.children(this.state)
      

props模式(5代码优化)

  • 推荐:给render props模式添加props校验

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

  • Mouse.propTypes {
      chidlren:PropTypes.func.isRequired
    }
    //如果是render的话也要对应的给render做校验
    Mouse.propTypes {
      render:PropTypes.func.isRequired
    }
    
  • componentWillUnmount(){
      window.removeEventListener('mousemove',this.handleMouseMove)
    }
    

高阶组件

介绍

  • 目的:实现状态逻辑复用

  • 采用包装(装饰)模式,比如说:手机壳

  • 手机:获取保护功能

  • 高阶组件就相当于手机壳,通过包装组件,增强组件功能

    思路分析

    • 高阶组件(HOC,Hight-OrderComponnet)是一个函数,接收要包装的组件,返回增强后的组件

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

    • const EnhancedComponent = withHOC(WrappedComponent)
      
    • //高阶组件内部创建的类组件
      class Mouse extends React.Component {
        render(){
          return <WrappedComponent {...this.state} />
        }
      }
      

使用步骤

  1. 创建一个函数,名称约定以with开头

  2. 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)

  3. 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回

  4. 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件

  5. 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中

  6. //创建高阶组件函数
    //withMouse 高阶组件函数
    //WrappedComponent 要被渲染的组件
    
    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)
        }
      	render (){
          return <WrappedComponent {...this.state}></WrappedComponent>
        }
      }
      
      return Mouse
    }
    
  7. // Mouse组件的render方法中
    return <WrappedComponent {...this.state} />
    
  8. //使用高阶组件
    //创建组件
    const MousePosition = withMouse(Position)
    
    //渲染组件
    <MousePosition />
    

设置displayName

  • 使用高阶组件存在的问题:得到的两个组件名称相同

  • 原因:默认情况下,React使用组件名称作为displayName

  • 解决方法:为高阶组件 设置displayName便于调试是区分不同的组件

  • displayName的作用:用于设置调试信息(ReactDeveloper Tools信息)

  • 设置方式

  • Mouse.displayName = `withMouse${getDisplayName(WrappedConent)}`
    
    funciton getDisplayName(WrappedComponent) {
      return WrappedComponent.displayName || WrappedComponent.name || 'Component'
    }
    

传递props

  • 问题:props丢失

  • 原因:高阶组件没有往下传递props

  • 解决方式:渲染WrappedComponent时,将state和this.props==一起传递给组件

  • 传递方式:

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

React组件进阶性总结

React原理揭秘

  • 能够知道setState()更新数据是异步的
  • 能够知道JSX语法的转化过程
  • 能够说出React组件的更新机制
  • 能够对组件进行性能优化
  • 能够说出虚拟DOM和Diff算法

setState()方法的说明

更新数据

  • setState()异步更新数据的

  • 注意:使用该语法时,后面的setState()不要依赖于前面的setState()

  • 可以多次调用setState(),但只会触发一次重新渲染(render函数只会触发一次)

  • this.state = {count:1}
    this.setState({
      count:this.state.count + 1      //再执行
    })
     console.log(this.state.count) //1   先执行
    

推荐语法

  • 推荐:使用setState((state,props) => {}) 语法

  • 参数state:表示最新的state

  • 参数props:表示最新的props

  • 这个也是异步更新state

  • this.setState((state,props) => {
      return {
        count:state.count + 1
      }
    })
     console.log(this.state.count) //1
    

第二个参数

  • 场景:在状态更新(页面完成重新渲染)后立即某个操作

  • this.setState(
    	(state,props) => {},   //第一个参数
      () => {console.log('这个回调函数会在状态更新后立即执行')}    //第二个参数
    )
    

JSX语法的转化过程

  • JXS仅仅是createElement()方法的语法糖(简化语法)
  • JSX语法被@babel/preset-react插件编译为createElement()方法
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGuzsXkX-1642920168828)(D:\资料\web笔记\react\image\笔记\image-20211227175716677.png)]

组件更新机制

  • ==setState()==的两个作用:1.修改state;2.更新组件(UI)
  • 过程:父组件重新渲染时,也会渲染子组件。但只会渲染==当前组件子树(当前组件及所有子组件)
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GWYz4Kt6-1642920168830)(D:\资料\web笔记\react\image\笔记\image-20211228100228710.png)]

组件性能优化

减轻state

state数据越多到时候渲染的性能就越低

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

避免不必要的重新渲染

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰

  • 问题:子组件没有任何变化时也会重新渲染

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

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

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

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

  • calss Hello extends Component {
      shouldComponentUpdate(nextProps,nextState){
        //nextprops,:最新的props
        // nextState:最新的状态
        //根据条件,决定是否重新渲染组件
        return false
      }
      render (){
        return 
      }
    }
    

避免不必要的重新渲染-随机数案例1-

随机生成10以内的数,如果这次和上次是重复的则不必重新渲染
calss Hello extends Component {
  state={
    count:0
  }
  handleClick = ()=> {
    this.setState(()=>{
      return {
        count:Mate.floor(Math.random()*10)
      }
    })
  }
  shouldComponentUpdate(nextProps,nextState){
    //nextprops,:最新的props
    // nextState:最新的状态
    
    //if(nextState.count === this.state.count) {
    // return true
    //}
    //return false
    return nextState.count === this.state.count
  }
  render (){
    return (
    	<div>
      	<p>随机数:{this.state.count}</p>
  			<button onClick={this.handleClick}>重新生成</button>
     	</div>
    )
  }
}

纯组件-shallow compare

  • 说明:纯组件内部的对比是shallow compare(浅层对比)

  • 对于引用类型来说:只比较对象的引用(地址)是否相同

  • 注意:stete或props中的属性值为引用类型时,应该创建新数据,不要直接修改原数据!(示例)

  • //正确的做法:创建新对象,  (新的对象和原来的对象的地址不同
    const newObj = {...this.state.obj,number:Math.floor(Math.random()*10)}
    
    this.setState (() => {
      return {
        obj:newObj
      }
    })
    
    

//假如是数组
//不要用数组的push / unshift 等直接修改当前数组的方法
//而应该用 concat 或 slice 等这些返回新数组的方法
this.setState ({
list: […this.state.list, {新数据}]
})


- ```js
//错误做法 :直接修改原始对象中的属性
const newObj = this.state.obj
newObj.number = Math.floot(Math.random() * 10)

this.setState (() => {
  return {
    obj:newObj
  }
})

虚拟DOM和Diff算法

  • React 更新视图的思想是:只要state变化就重新渲染视图
  • 特点:思路非常清晰
  • 问题:组件中只有DOM元素需要更新时,也得把整个组件的内容重新渲染到页面中?不是
  • 理想状态:部分更新,只更新变化的地方
  • 问题:React是如何做到部分更新的?虚拟DOM配合Diff算法
  • NULL

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

  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wNWxpWNo-1642920168832)(D:\资料\web笔记\react\image\笔记\image-20211228165817263.png)]

执行过程

  1. 初次渲染时,React会根据初始state(Model),创建一个虚拟DOM对象(数)

  2. 根据虚拟DOM生成真正的DOM,渲染到页面中

  3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)

  4. 与上一次得到的虚拟DOM对象,使用Diff算法对比(找不同),得到需要更新的你内容

  5. 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面 //patch(打补丁)

  6. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xO17n2Ex-1642920168833)(D:\资料\web笔记\react\image\笔记\image-20211228171911378.png)]

  7. 白话:初始渲染时,React会根据初始state状态,去生成一个虚拟dom对象(其实就是一个js对象),然后根据虚拟DOM对象去重新生成真正的浏览器DOM对象,渲染到页面中,这样就能看到这个组件初次渲染的内容了。当我们在React中调了setState()进行更新之后,那么首先状态会发生更新,状态一发生更新,那么React内部就会根据最新的状态生成一个新的虚拟DOM对象,很明显这个新的虚拟DOM就会反映状态的一个变化,所以新的虚拟DOM对象也是和上次的虚拟DOM对象是存在差异的,这样的话我们就得到了,新的和旧的两个虚拟DOM了,接下来通过Diff算法来对比新、旧两个虚拟DOM对象,这样的话就能找到他们两个虚拟DOM的不同地方了,找到这个不同的地方之后,最后只把不同的地方对应的那个DOM来进行重新渲染一下,那么就可以看到页面中某一部分发生变化了,变化其实是只把需要更新的地方进行了更新,不需要更新的内容是没有发生过变化的,这样就做到了部分更新

  8. React通过了虚拟DOM和Diff算法来高效的更新

虚拟DOM和Diff算法(代码演示)

  • 组件render()调用后,根据状态JSX结构生成虚拟DOM对象

React原理揭秘总结

  • 工作角度:应用第一,原理第二
  • 原理有助于更好地理解React的自身运行机制
  • setState()异步更新数据
  • 父组件更新导致子组件更新,纯组件提升性能 / 钩子函数 shouldComponentUpdate(nextProps,nextState)
  • 思路清晰简单为前提,虚拟DOM和Diff保效率
  • 虚拟DOM →state+JSX
  • 虚拟DOM的真正价值从来都不是性能
  • 虚拟DOM最大的好处:脱离了浏览器的环境束缚,虚拟DOM其实就是个js对象,哪里可以运行js哪里就可以运行react

React路由基础

译:

  1. pathname: //to属性:浏览器地址栏中的pathname(location.pathname) 页面一
  • React路由介绍
  • 路由的基本使用
  • 路由的执行过程
  • 编程式导航
  • 默认路由
  • 匹配模式

React路由介绍

现代的前端应用大多数都是SPA(单页面应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器的压力更小,所以更受欢迎。为了有效的使用单个页面来管理原理多个页面的功能,前端路由应运而生。

  • 前端路由的功能:让用户从一个视图(页面)导航到另一个视图(页面)
  • 前端路由是一套映射规则,在React中,是URL组件的对应关系
  • 使用React路由简单来说,就是配置路径组件(配对)

React路由基本使用

  1. 安装:yarn add react-router-dom

  2. 导入路由的三个核心组件:Router/Route/Link

    //BrowserRouter 取别名为 Router
    import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
    
    
  3. 使用Router组件包裹整个应用(重要)

    <Router>
      <div className="App">
        //...省略内容
       </div>
    </Router>
    
  4. 使用Link组件作为导航菜单(路由入口)

    <Link to="/first">页面一</Link>
    
  5. 使用Route组件配置路由规则和要展示的组件(路由出口)

    const First = () => <p>页面一的内容</p>
    
      <Router>
        <div className = "App">
      		<Link to="/first">页面一</Link>
    			<Route push="/first" component={Fisrt}>
        </div>
    	</Router>
    
  6. null

  7. import React from 'react'
    import ReactDom form 'react-dom'
    
    //react-router-dom 的基本使用
    
    //导入组件:
    import {BrowserRouter as Router,Route,Link} from 'react-router-dom'
    
    const First = () => <p>页面一的内容</p>
    
    //使用router包裹整个应用
    const App = () => {
      <Router>
        <div>
          <h1>Router路由基础</h1>
          //指定路由入口
      		<Link to="/first">页面一</Link>
      
      		//指定路由出口
      		<Route path="/first" component={First} />
        </div>
    	</Router>
    }
    
    ReactDOM.render(<App />,document.getElementById('root'))
    

React路由基本使用(常用组件说明)

  • Router组件:包裹整个应用,一个React应用只需要使用一次

  • 两种常用Router: HashRouter和BrowserRouter

  • HashRouter:使用URL的哈希值实现(localhost:3000/#/first)

  • (推荐)==BrowserRouter:==使用H5的history API实现(localhost:3000/first)

    //使用HashRouter
    import {HashRouter as Router,Route,Link} from 'react-router-dom'
    //使用BrowserRouter
    import {BrowserRouter as Router,Route,Link} from 'react-router-dom'
    
  • Link组件:用于指定导航链接(a标签)

    //to属性:浏览器地址栏中的pathname(location.pathname)
    <Link to="/first">
    
  • Route组件:指定路由展示组件的相关信息

    //path属性:路由规则
    //component属性:展示的组件
    //Router组件写在哪,渲染出来的组件就展示在哪
    <Route path="/first" component={Fisrt}></Route>
    
  • null

React路由执行过程

  • 点击Link组件(a标签),修改了浏览器地址栏中的url
  • React路由监听到地址栏url的变化
  • React路由内部遍历所有Route组件,使用路由规则(path) 与pathname进行匹配 / location.pathname
  • 当路由规则(path)能够匹配地址栏中的pathname是,就展示该Route组件的内容
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R44k8tlX-1642920168836)(D:\资料\web笔记\react\image\笔记\image-20211229163313420.png)]

编程式导航

  • 场景:点击登录按钮,登录成功后,通过代码跳转到后台首页,如何实现?

  • 编程是导航:通过JS代码来实现页面跳转

  • history是React路由提供的,用于获取浏览器历史记录的相关信息

  • ==push(path):==跳转到某个页面,参数path表示要跳转的路径

  • go(n):前进或后退到某个页面,参数n表示前进或后退页面数量(比如:-1表示后退到上一页)

    class Login extends Component {
      handleLogin = () => {
        // ...
        this.props.history.push('/home')
      }
      render(){...省略其他代码}
    }
    

默认路由

  • 问题:现在的路由都是点击导航菜单后展示的,如何在进入页面的时候就展示呢?

  • 默认路由:表示进入页面时就会匹配的路由

  • 默认路由path为:/

  • <Route path="/" component={Home} />
    

匹配模式(1模糊匹配模式)

  • 问题:当Link组件的to属性为“/login”时,为什么默认路由也被匹配成功?
  • 默认情况下,React路由是模糊匹配模式
  • 模糊匹配规则:只要pathname以path开头就会匹配成功
<Link to="/login">登录成功</Link>
<Route path="/" component={Home} /> //匹配成功
//path 代表Route组件的path属性
//pathname 代表Link组件的to属性(也就是locatio.pathname)
path能够匹配的pathname
/所有pathname
/first/first 或/first/a 或/first/a/b/…

匹配模式(2精确匹配)

  • 问题:默认路由任何情况下都会展示,如何避免这种问题?

  • 给Route组件添加exact属性,让其变为精确匹配模式

  • 精确匹配:只有但pathpathname完全匹配时才会展示该路由

  • 推荐:给默认路由添加exact属性

    //此时,该组件只能匹配pathname="/"这一种情况
    <Route exact path="/" component={...}>
    

React路由基础总结

  • React路由可以有效的管理多个视图(组件)实现SPA
  • Router组件包裹整个应用,只需使用一次
  • Link组件是入口,Route组件是出口
  • 通过props.history实现编程式导航
  • 默认模糊匹配,添加exact变精确匹配
  • React路由的一切都是组件,可以像思考组件一样思考路由

项目

项目准备

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cfZ8JjgR-1642920168837)(D:\资料\web笔记\react\image\笔记\image-20211230170936995.png)]

组件库antd-mobile

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxNBq8AY-1642920168839)(D:\资料\web笔记\react\image\笔记\image-20211230172501453.png)]

配置基础路由

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nSeNshKV-1642920168840)(D:\资料\web笔记\react\image\笔记\image-20211230172945578.png)]

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值