react基础

React 基础

认识 React

在这里插入图片描述

  • React 中文文档1(国内社区):https://react.docschina.org/
  • React 中文文档2(官方):https://zh-hans.reactjs.org

React 概述

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

React 起源于 Facebook ,并于 2013 年 5 月开源

React 本身只关注界面, 其它如:前后台交互、路由管理、状态管理等都由其扩展插件或其它第三方插件搞定

React 几个特点

  1. 声明式编码

  2. 组件化编码

    • 将一个较大较复杂的界面拆分成几个可复用的部分封装成多个组件, 再组合使用

    • 组件可以被反复使用

  3. 引入了 JSX 语法,编写高效

  4. 内置 diff 算法,运行高效

  5. 一次学习,随处编写

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

React 开发的网站

  • MDN: https://developer.mozilla.org/zh-CN/
  • 知乎: https://www.zhihu.com/
  • 阿里云: https://www.aliyun.com/

React基本使用

基本使用步骤

在这里插入图片描述

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

    <!-- react库, 提供React对象 -->
    <script src="../js/react.development.js"></script>
    <!-- react-dom库, 提供了ReactDOM对象 -->
    <script src="../js/react-dom.development.js"></script>
    
  2. 在html定义一个根容器标签

    <div id="root"></div>
    
  3. 创建react元素(类似html元素)

    // 返回值:React元素 
    // 参数1:要创建的React元素名称 =》字符串
    // 参数2:元素的属性  =》对象 {id: 'box'} 或者 null
    // 后面参数:该React元素的所有子节点 =》文本或者其他react元素
    const element = React.createElement(
      'h1', 
      {title: '你好, React!'}, 
      'Hello React!'
    )
    
  4. 渲染 react 元素

    // 渲染React元素到页面容器div中
    ReactDOM.render(element, document.getElementById('root'))
    

特殊属性

在这里插入图片描述

  • class ==> className
const element = React.createElement(
  'h1', 
  {
    title: '你好, React!',
    className: 'active'
  }, 
  'Hello React!'
)

再来个复杂点的

在这里插入图片描述

const title = '北京疫情'
const content = '北京这段时间疫情还在持续中...'

const vDom = React.createElement('div', null, 
  React.createElement('h2', {title}, '你关注的北京疫情'),
  React.createElement('p', null, content)
)
ReactDOM.render(vDom, document.getElementById('root2'))

理解 React 元素

  1. 也称虚拟 DOM (virtual DOM) 或虚拟节点(virtual Node)

  2. 它就是一个普通的 JS 对象, 它不是真实 DOM 元素对象

    虚拟 DOM: 属性比较少 ==> 较轻的对象

    真实 DOM: 属性特别多 ==> 较重的对象

  3. 但它有一些自己的特点

    虚拟 DOM 可以转换为对应的真实 DOM => ReactDOM.render方法将虚拟DOM转换为真实DOM再插入页面

    虚拟 DOM 对象包含了对应的真实 DOM 的关键信息属性

    属性示例
    标签名type: “h1”
    标签属性props: {title: ‘你好, React!’}
    子节点props: {children: ‘Hello React!’}

JSX

基本理解和使用

问题: React.createElement() 写起来太复杂了

解决: 推荐使用更加简洁的JSX

JSX 是一种 JS 的扩展语法, 用来快速创建 React 元素(虚拟DOM/虚拟节点)

形式上像 HTML 标签/任意其它标签, 且标签内部是可以套 JS 代码的

const h1 = <h1 className="active">哈哈哈</h1>   

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

线上测试: https://www.babeljs.cn/

<!-- 必须引入编译jsx的babel库 -->
<script src="../js/babel.min.js"></script>

<!-- 必须声明type为text/babel, 告诉babel对内部的代码进行jsx的编译 -->
<script type="text/babel">
	// 创建React元素 (也称为虚拟DOM 或 虚拟节点)
	const vDom = <h1 title="你好, React2!" className="active">Hello React2!</h1>
    // 渲染React元素到页面容器div中
    ReactDOM.render(vDom, document.getElementById('root'))
</script>

注意:

​ 必须有结束标签
​ 整个只能有一个根标签
​ 空标签可以自闭合

JSX 中使用 JS 表达式

  • JSX 中使用 JS 表达式的语法:{}
  • 作用: 指定动态的属性值和标签体文本
  1. 可以是任意基本类型数据值, 但 null、undefined 和布尔值没有任何显示
  2. 子元素可以是一个 JS 数组, 但不能是 JS 对象
  3. style 属性值必须是一个包含样式的 JS 对象
  4. 可以是 JS 的表达式, 不能是 JS 的语句
  5. 可以是 react 元素对象
let title = 'I Like You'
const vNode = (
  <div>
    <h3 name={title}>{title.toUpperCase()}</h3>
    <h3>{3}</h3>
    <h3>{null}</h3>
    <h3>{undefined}</h3>
    <h3>{true}</h3>
    <h3>{'true'}</h3>
    <h3>{React.createElement('div', null, 'atguigu')}</h3>
    <h3>{[1, 'abc', 3]}</h3>
    <h3 title={title} id="name" className="ative" style={{color: 'red'}}></h3>
    {/* <h3>{{a: 1}}</h3> */} 
  </div>
)

样式处理

行内样式

  • 样式属性名使用小驼峰命名法
  • 如果样式是数值,可以省略单位
<h2 style={{color: 'red', fontSize: 30}}>React style</h2>

类名

  • 必须用 className, 不能用 class
  • 推荐, 效率更高些
<h2 className="title">React class</h2>

条件渲染

if…else

let vDom
if (isLoading) {
  vDom = <h2>正在加载中...</h2>
} else {
  vDom = <div>加载完成啦!</div>
}
 ReactDOM.render(vDom, document.getElementById('root'))

三元表达式

const vDom = isLoading ? <h2>正在加载中2...</h2> : <div>加载完成啦2!</div>
ReactDOM.render(vDom, document.getElementById('root'))

&&

const vDom = isLoading && <h2>正在加载中3...</h2>
ReactDOM.render(vDom, document.getElementById('root'))
// 注意: 只适用于只在一种情况下才有界面的情况

列表渲染

  • react中可以将数组中的元素依次渲染到页面上
  • 可以直接往数组中存储react元素
  • 推荐使用数组的 map 方法
  • 注意:必须给列表项添加唯一的 key 属性, 推荐使用id作为key, 尽量不要用index作为key

需求: 根据下面的数组显示列表

const courses = [
{id: 1, name: ‘React’},
{id: 3, name: ‘Vue’},
{id: 5, name: ‘小程序’}
]

在这里插入图片描述

const courses = [
  {id: 1, name: 'React'},
  {id: 3, name: 'Vue'},
  {id: 5, name: '小程序'}
]

const vDom = (
  <div>
    <h2>前端框架课程列表</h2>  
    <ul>
      {courses.map(c => <li key={c.id}>{c.name}</li>)}  
    </ul>
  </div>
)

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

事件处理

绑定事件

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

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

事件对象

React 根据 W3C 规范来自定义的合成事件, 与原生事件不完全相同

  • 处理好了浏览器的兼容性问题

  • 阻止事件默认行不能使用 return false, 必须要调用: event.preventDefault()

  • 有自己特有的属性, 比如: nativeEvent – 原生事件对象

  • <input>的 change 监听在输入过程中触发, 而原生是在失去焦点才触发

    • 原理:内部绑定的是原生 input 事件
function handleClick1(event) {
  console.log(event)
  alert(event.target.innerHTML)
}

const handleClick2 = (event) => {
  const isOdd = Date.now()%2===1
  alert(isOdd)
  if (!isOdd) {
    // return false // 不起作用
    event.preventDefault()
  }
}

const vDom = <div>
    <button onClick={handleClick1}>点击提示按钮文本</button>
    <br/>
    <br/>
    <a href="http://www.baidu.com" onClick={handleClick2}>奇数才去百度</a>
  </div>

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

案例

  • 需求:实现评论列表功能
    • 如果有评论数据,就展示列表结构 li( 列表渲染 )要包含a标签
      • name 表示评论人,渲染 h3
      • content 表示评论内容,渲染 p
    • 如果没有评论数据,就展示一个 h1 标签,内容为: 暂无评论!
    • 用户名的字体25px, 内容的字体20px
    • 点击内容区域提示它发表的时间
const list = [
  { id: 1, name: 'jack', content: 'rose, you jump i jump', time: '03:21' },
  { id: 2, name: 'rose', content: 'jack, you see you, one day day', time: '03:22' },
  { id: 3, name: 'tom', content: 'jack,。。。。。', time: '03:23' }
]

在这里插入图片描述

React的组件

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

组件允许你将 UI 拆分为独立可复用的代码片段,包括JS/CSS/IMG等。

组件从概念上类似于 JavaScript 函数。它接收参数(即 “props”),内部可以有自己的数据(即 “state”),并返回用于描述页面展示的 React 元素。

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

创建组件的两种方式

函数组件

 
function App() {
  // return null
  return <div>App</div>
}

// 函数名就是组件名
ReactDom.render(<App />, document.getElementById('root')) 
  1. 组件名首字母必须大写. 因为react以此来区分组件元素/标签 和 一般元素/标签

  2. 组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签

  3. 必须有返回值.返回的内容就是组件呈现的结构, 如果返回值为 null,表示不渲染任何内容

  4. 会在组件标签渲染时调用, 但不会产生实例对象, 不能有状态

注意: 后面我们会讲如何在函数组件中定义状态 ==> hooks语法

类组件

import React from "react"

class App extends React.Component {
  render () {
    return <div>App Component</div>
  }
}

ReactDom.render(<App />, document.getElementById('root'))
  1. 组件名首字母必须大写.

  2. 组件内部如果有多个标签,必须使用一个根标签包裹.只能有一个根标签

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

  4. 类组件中必须要声明一个render函数, reander返回组件代表组件界面的虚拟DOM元素

  5. 会在组件标签渲染时调用, 产生实例对象, 可以有状态

React 脚手架

React 脚手架介绍

React 脚手架是官方提供的 React 开发工具。

脚手架开发项目的特点:

  1. 更安全
    1. 包含了 eslint 配置,实时提醒代码异常
  2. 更方便
    1. 包含了所有需要的配置(jsx编译、devServer…)
    2. 下载好了 react 的依赖包(react,react-dom,babel…)
    3. 搭建好了目录结构
  3. 更高效
    1. 实时预览代码运行效果
    2. 打包优化处理

创建 React 项目

使用 create-react-app 创建项目的流程:

  1. 下载

    npm i create-react-app -g
    
  2. 创建项目命令

    create-react-app hello-react
    
  3. 命令行进入项目文件夹

    cd hello-react
    
  4. 启动服务

    npm start
    

从 V18 降级到 V17 的版本

最新的脚手架默认使用的是最新的 React18 的版本, 而这个版本是最近才出稳定版, 企业项目还未开始使用

如何降级到 V17 的最新版呢?

  1. 重新下载 react 和 react-dom, 并指定 17 的版本

    npm i react@17 react-dom@17
    
  2. 修改入口JS的实现

    import React from 'react'
    import ReactDOM from 'react-dom'
    
    import App from './App'
    
    ReactDOM.render(<App />, document.getElementById('root'))
    

类式组件的状态 state

介绍

state (状态)即数据,是类式组件实例对象的属性,只能在组件内部使用 .

state 属性值为对象, 可以在 state 对象中保存多个数据

作用

state 属性值改变之后能重新渲染组件, 方便更新页面

基本使用

  • 初始化 state
    • 构造器中: this.state = {xxx: 2}
    • 类体中: state = {xxx: 2}
  • 读取state数据
    • this.state.xxx
  • 更新state数据
    • 不能直接更新state数据
    • 必须 this.setState({ 要修改的属性数据 })

在这里插入图片描述

class StateTest extends React.Component {

  /* constructor () {
    super() // 必须调用super()
    // 初始化state
    this.state = {
      count: 0,
      xxx: 'abc'
    }
  } */
  // 初始化状态(简洁语法)
  state = {
    count: 0,
    xxx: 'abc'
  }


  render () {
    // 读取state数据
    const {count} = this.state

    return <div onClick={() => {
      // 直接更新状态数据 => 界面不会自动更新  不可用
      // this.state.count = count + 1
      
      // 通过setState()更新state => 界面会自动更新
      this.setState({
        count: count + 1
      })
    }}>点击的次数: {count}</div>
  }
}

事件回调 this 问题

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

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

问题: 类中定义的事件回调方法中thisundefined, 无法更新state

原因: 事件回调都不是组件对象调用的, 都是事件触发后,直接调用的,
class中所有方法都使用严格模式, 所以方法中的this就是undefined

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C3ppXRAc-1658143343435)(images/image-20220520222119252.png)]

  • 基础代码:组件的虚拟DOM

    <div>
      <h3>当前count为: 0</h3>
      <button>点击报错,this问题</button><br/>
      <button>解决办法1 - 包裹箭头函数</button><br/>
      <button>解决办法2 - bind绑定this </button><br/>
      <button>解决办法3 - 箭头函数</button>
    </div>
    

解决办法1 - 包裹箭头函数
原因: render中的this是组件对象, 处理函数是我们通过组件对象来调用的

解决办法2 - bind绑定this
原因: 构造器中的this是组件对象, 将处理函数通过bind绑定为了组件对象

解决办法3 - 箭头函数
原理: 利用bind给事件回调绑定this为组件对象(render中的this)

class EventThis extends React.Component {

  constructor () { // 构造器中的this是组件对象
    super()
    /* 
    this.handle4 = () =>  {
      console.log(this)
      this.setState({
        count: this.state.count + 3
      })
    } */
  }
  
	// 初始化state
  state = { count: 0 }

  /* 
  问题: 类中定义的事件回调方法中this是undefined, 无法更新state
  原因: babel编译jsx. 采用的是严格模式, 事件监听函数中this就指向undefined
  */
  handle1 () {
    console.log(this) // this是undefined
    this.setState({ // 报错
      count: this.state.count + 1
    })
  }

  handle2 () {
    console.log(this)
    this.setState({
      count: this.state.count + 2
    })
  }

  handle3 () {
    console.log(this)
    this.setState({
      count: this.state.count + 3
    })
  }

  /* 
  解决办法3 - 箭头函数
  原因: 改为箭头函数后, 变为了给组件对象添加属性, 且是在构造器中执行的, 用的就是构造函数中的this
  */
  handle4 = () =>  {
    console.log(this)
    this.setState({
      count: this.state.count + 3
    })
  }


  render () { // render中的this是组件对象
    console.log('render()', this.state.count)
    
    return (
      <div>
        <h3>当前count为: {this.state.count}</h3>
        <button onClick={this.handle1}>点击报错,this问题</button>
        {/* 
        解决办法1 - 包裹箭头函数
        原因: render中的this是组件对象, 处理函数是我们通过组件对象来调用的
        */}
        <button onClick={() => {this.handle2()}}>解决办法1 - 包裹箭头函数</button>
        <button onClick={this.handle3.bind(this)}>解决办法2 - bind绑定this </button>
        <button onClick={this.handle4}>解决办法3 - 箭头函数</button>
      </div>
    )
  }
}

选择:

  1. 一般用箭头函数方式, 编码简洁
  2. 如果要传递特定的数据, 选择用 包裹箭头函数方式

组件的 props

介绍

props 是 『properties』 的缩写, 也是类式组件实例对象的属性

作用

props用来接收组件外部数据,实现组件的解耦与复用

基本使用

<Person name="练习生" age="28" gender=""  />
<Person {...kun} />

props 的特点

  1. 可以给组件传递任意类型的数据
  2. props 是只读的对象,只能读取属性的值,不要修改props
  3. 可以通过...运算符来将对象的多个属性分别传入子组件
  4. 如果父组件传入的是动态的 state 数据, 那一旦父组件更新 state 数据, 子组件也会更新

在这里插入图片描述

  • 子组件

    // 函数组件
    export function FunProps(props) {
      return <h2>FunProps-个人信息: 姓名: {props.name}, 年龄: {props.age}</h2>
    }
    
    // 类组件
    export class ClassProps extends React.Component {
      render () {
        const { myName, age} = this.props
        return <h2>ClassProps-个人信息: 姓名: {myName}, 年龄: {age}</h2>
      }
    }
    
  • 父组件

    class App extends React.Component {
      state = {
        person: {
          myName: 'tom',
          age: 12
        }
      }
    
      render () {
        const {myName, age} = this.state.person
        return <div>
            <p>人员信息: {myName + ' : ' +age}</p>
            <button onClick={() => {
              this.setState({
                person: { myName: myName+'--', age: age+1}
              })
            }}>更新人员信息</button>
            <br/>
    
            <FunProps name={myName} age={age}/>
            <hr/>
            {/* <ClassProps myName={myName} age={age}/> */}
            <ClassProps {...this.state.person}/>
          </div>
      }
    }
    

props 校验 (了解)

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

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

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

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

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

实现方式:

  1. 导入 prop-types 包

  2. 使用propTypes来给组件的props添加校验规则

// 常见类型:string、bool、number、object、array、func还有element(react元素)
// 必填项:isRequired 
// 特定结构的对象:shape({  }) 
import React from 'react'
import PropTypes from 'prop-types'

export default class PropCheck extends Component {

  // 指定属性名/属性值类型/属性的必要性
  static propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired,
    likes: PropTypes.array,
  }

  // 指定属性的默认值
  static defaultProps = {
    likes: ['A', 'B', 'C']
  }

  render () {
    const { name, age, likes} = this.props
    return <div>
      <h2>PropCheck-个人信息</h2>
      <p>姓名: {name}</p>
      <p>年龄: {age}</p>
      <p>爱好: {likes.join('-')}</p>
    </div>
  }
}

// 函数组件只能这么指定
// FunctionProps.propTypes = {}
// FunctionProps.defaultTypes = {}


<ClassProps myName={myName} age={age} />

props 默认值

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

实现方式:

function App(props) { 
  return ( 
    <div> 
      此处展示props的默认值:{props.pageSize} 
    </div> 
  ) 
} 
// 设置默认值 
App.defaultProps = { 
  pageSize: 10 
} 
// 不传入pageSize属性 
<App /> 

收集表单数据

非受控组件

表单项不与state数据相向关联, 需要手动读取表单元素的值

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

ref 的作用:获取 DOM 或组件对象

<form>
  <h2>登陆页面</h2>
  用户名: <input type="text"/> <br/>
  密  码: <input type="password"/> <br/>
  <input type="submit" value="登 陆"/>
</form>

在这里插入图片描述

import React, { Component } from 'react'
/* 
非受控组件: 
  表单项不与 state 数据相向关联, 需要手动读取表单元素的值
编码过程
  1. 初始化创建 ref 容器并保存到组件对象上
  2. 将 ref 容器通过 ref 属性交给表单项标签 => 渲染时内部会将对应的真实DOM保存到 ref 容器的 current属性上
  3. 点击提交按钮时, 通过 ref 容器的 current 属性得到 input DOM 元素 => 就可以读取其 value了
不足
  不够自动化/无法进行实时数据校验
*/
export default class FormTest extends Component {
  // 1. 初始化创建ref容器并保存到组件对象上
  nameRef = React.createRef()
  ageRef = React.createRef()

  submit = (e) => {
    // 阻止事件的默认行为(提交表单)
    e.preventDefault()
    // 3. 点击提交按钮时, 通过ref容器的current属性得到input DOM元素 => 就可以读取其value了
    console.log(this.nameRef.current)
    const name = this.nameRef.current.value
    const age = this.ageRef.current.value
    alert(`提交登陆的ajax请求 name=${name}, age=${age}`)
  }

  render() {
    return (
      <form action=''>
        <h2>登陆页面(非受控组件)</h2>
        {/* 2. 将ref容器通过ref属性交给表单项标签 => 渲染时内部会将对应的真实DOM保存到ref容器的current属性上 */}
        用户名: <input type="text" ref={this.nameRef}/> <br/>
        密  码: <input type="password" ref={this.ageRef}/> <br/>
        <input type="submit" value="登 陆" onClick={this.submit}/>
      </form>
    )
  }
}

受控组件

组件中的表单项根据状态数据动态初始显示和更新显示, 当用户输入时实时同步到状态数据中

也就是实现了页面表单项与 state 数据的双向绑定

实现方式

  1. 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)
  2. 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)
/* 
受控组件
*/
export class FormTest2 extends React.Component {
  state = {
    name: 'admin',
    pwd: '123456'
  }

  submit = (e) => {
    // 阻止事件的默认行为(提交表单)
    e.preventDefault()
    const {name, pwd} = this.state
    alert(`提交登陆请求 name=${name}, pwd=${pwd}`)
  }

  handleNameChange = (e) => {
    this.setState({
      name: e.target.value
    })
  }

  handlePwdChange = (e) => {
    this.setState({
      pwd: e.target.value
    })
  }

  render () {
    const {name, pwd} = this.state

    return (
      <form action=''>
        <h2>登陆页面(受控组件)</h2>
        用户名: <input type="text" value={name} onChange={this.handleNameChange}/> <br/>
        密  码: <input type="password" value={pwd} onChange={this.handlePwdChange}/> <br/>
        <input type="submit" value="登 陆" onClick={this.submit}/>
      </form>
    )
  }
}

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

优化1: 使用同一个事件函数处理

问题: 2个input的onChange事件处理函数代码重复

解决: 使用一个事件函数

/* 
受控组件=> 优化
*/
export class FormTest3 extends React.Component {
  state = {
    name: 'admin',
    pwd: '123456'
  }

  submit = (e) => {
    // 阻止事件的默认行为(提交表单)
    e.preventDefault()
    const {name, pwd} = this.state
    alert(`提交登陆请求 name=${name}, pwd=${pwd}`)
  }

  handleChange = (e, prop) => {
    this.setState({
      [prop]: e.target.value
    })
  }

  render () {
    const {name, pwd} = this.state

    return (
      <form action=''>
        <h2>登陆页面(优化)</h2>
        用户名: <input type="text" value={name} 
    			onChange={e => this.handleChange(e, 'name')}/> <br/>
        密  码: <input type="password" value={pwd} 
					onChange={e => this.handleChange(e, 'pwd')}/> <br/>
        <input type="submit" value="登 陆" onClick={this.submit}/>
      </form>
    )
  }
}

优化2: 利用高阶函数

/* 
受控组件=> 优化2
*/
export class FormTest4 extends React.Component {
  state = {
    name: 'admin',
    pwd: '123456'
  }

  submit = (e) => {
    // 阻止事件的默认行为(提交表单)
    e.preventDefault()
    const {name, pwd} = this.state
    alert(`提交登陆请求 name=${name}, pwd=${pwd}`)
  }

  handleChange = (prop) => {
    return (e) => {
      this.setState({
        [prop]: e.target.value
      })
    }
  }

  render () {
    const {name, pwd} = this.state

    return (
      <form action=''>
        <h2>登陆页面(优化2)</h2>
        用户名: <input type="text" value={name} onChange={this.handleChange('name')}/> <br/>
        密  码: <input type="password" value={pwd} onChange={this.handleChange('pwd')}/> <br/>
        <input type="submit" value="登 陆" onClick={this.submit}/>
      </form>
    )
  }
}

组件的生命周期

生命周期图谱: https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

在这里插入图片描述

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

挂载阶段

流程: constructor ==> render ==> componentDidMount

触发: ReactDOM.render(): 渲染组件元素

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

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

componentDidMount: 组件挂载(完成DOM)渲染后 一般用于: 1. 发送网络请求 2. DOM操作 (只要是组件以上来就要做的事情,都应该写在挂载成功的回调中)

更新阶段

流程: render ==> componentDidUpdate

触发: setState() , forceUpdate(), 组件接收到新的props

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

卸载阶段

流程: componentWillUnmount

触发: 不再渲染组件

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

旧版react生命周期(了解)

componentWillMount

componentWillUpdate,

componentWillReceiveProps

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

Hook

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • Hook 也叫钩子,本质就是函数,能让你在函数式组件中使用状态生命周期函数等功能
  • Hook 语法 基本已经代替了类组件的语法,后面的 React 项目就完全是用 Hook 语法了

useState()

用来定义状态数据,可以多次调用, 产生多个状态数据

import { useState } from 'react'

export default function App() {
  console.log('App')
  /* 
  1. 声明一个state数据变量, 名称任意 xxx
  2. 声明一个更新state数据的函数, 名称一般: setXxx 
  3. useState参数指定初始值
  4. 后面更新调用时返回内部存储的最新state数据
  */
  const [count, setCount] = useState(0)

  return <div>
      <p>点击次数: { count }</p>
      <button onClick={() => setCount(count + 1)}>点击增加一次</button>
      <button onClick={() => setCount(count => count + 2)}>点击增加二次</button>
    </div>
}

useEffect()

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

相当于componentDidMount,componentDidUpdate 和 componentWillUnmount的组合

import {useState, useEffect} from 'react'

export default function Test(props) {
  const [msg, setMsg] = useState('--')
  const [count, setCount] = useState(1)

  /* 相当于 componentDidMount */
  useEffect(() => {
    setMsg(localStorage.getItem('msg_key') || '')
  }, [])

  /* 相当于 componentDidMount + msg更新后的componentDidUpdate */
  useEffect(() => {
    localStorage.setItem('msg_key', msg)
  }, [msg])

  /* 相当于 componentDidMount */
  useEffect(() => {
    const id = setInterval(() => {
      console.log('========')
      /* setCount(count + 1)
      document.title = count + 1 */

      setCount(count => {
        count  += 1
        document.title = count
        return count
      })
    }, 1000)

    /* 相当于 componentWillUnmount */
    return () => {
      clearInterval(id)
    }
  }, [])

  return (
    <div>
      <h3>state.count: {count}</h3>
      <h3>state.msg: {msg}</h3>
      <button onClick={() => setMsg(msg + '-')}>更新msg</button>
    </div>
  )
}

Hook 注意:

  1. 只在最顶层使用 Hook,不要在条件或循环中

  2. 只在React组件函数内部中调用 Hook, 不要在组件函数外部调用

案例- TODO List 案例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UghCVKBI-1658143343438)(images/image-20220523075242606.png)]

功能描述

  1. 动态显示初始列表
  2. 添加一个 todo
  3. 删除一个 todo
  4. 反选一个 todo
  5. todo 的全部数量和完成数量
  6. 全选/全不选 todo
  7. 删除完成的 todo

AJAX

  • 在 react 项目开发中,基本都是使用 axios 库来发送 AJAX 请求与后台通信

  • 而发 AJAX 请求的时机分下面三种情况

    • 初始化发一次请求: 在componentDidMount中发送

    • 事件回调函中发送请求

    • 在某个 state 或 props 数据发生改变后发送请求: 在 componentDidUpdate 中发送

  • 接口1: https://api.github.com/search/repositories?q=re&sort=stars

  • 接口2: https://api.github.com/search/users?q=a

测试案例一

在这里插入图片描述

import React from "react"
import axios from 'axios'

class App extends React.Component {

  state = {
    url: '',
    repoName: ''
  }

  async componentDidMount () {
    const response = await axios('https://api.github.com/search/repositories?q=re&sort=stars')
    const {name, html_url} = response.data.items[0]
    this.setState({
      url: html_url,
      repoName: name
    })
  }

  render () {
    const { url, repoName } = this.state
    if (!url) {
      return <h3>正在加载中...</h3>
    }
    return <div>
      最受欢迎的仓库是: <a href={url}>{repoName}</a>
    </div>
  }
}

测试案例二

在这里插入图片描述

搭建后台接口

  • server.js
  • 使用 node + express + axios
/* 
后台服务器应用模块: 使用express快速搭建后台路由
*/

const express = require('express')
const axios = require('axios')
const app = express()

// 能解析urlencode格式的post请求体参数
app.use(express.urlencoded())
// 能解析json格式的请求体参数
app.use(express.json())

// 根路径路由
app.get('/', (req, res) => {
  res.send({status: 1, data: '我是测试数据'})
})

// 搜索用户的路由
app.get('/search/users', (req, res) => {
  console.log('host', req.headers['host'])
  const q = req.query.q
  axios.get('https://api.github.com/search/users', {
    params: {q}
  })
    .then(response => {
      const result = response.data
      res.send(result)
    }).catch(error => {
      console.log(error.message)
    })
})

// 启动监听服务
app.listen('4000', () => {
  console.log('server listen on http://localhost:4000')
})

react脚手架配置代理

在package.json中追加如下配置

"proxy":"http://localhost:4000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。=> 后面项目中我们会讲解配置多个代理的方式
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

组件通讯

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

props

单向数据流: 非函数属性通过标签属性, 由外层组件逐级传递给内层组件

父子间通信
祖孙间通信
兄弟间通信
在这里插入图片描述

在这里插入图片描述

context

与任意后代直接通信

一般应用中不使用, 但一些插件库内部会使用context封装, 如: react-redux
在这里插入图片描述

  • 调用 React. createContext() 创建 context 对象

    const context = React.createContext() 
    
  • 外部组件中使用 context 上的 Provider 组件作为父节点, 使用value属性定义要传递的值

    <context.Provider value={要传递的值}>  提供数据
      <div className="App"> 
        <Child1 /> 
      </div> 
    </context.Provider>
    
  • 任意后代组件中, 读取Provider 组件提供的数据

    方式一:

    function Child () {
    	return <div>
    		<context.Consumer>   消费者
        	{
            data => <p>{ data }</p>
          }
        </context.Consumer>
    	</div>
    }
    

    方式二:

    class Child extends Component {
    	// 将context对象保存到contextType静态属性上 
      // => 内部会自动将数据保存到组件对象的context属性上
      static contextType = context
      render() {
        // 通过context属性得到Provider提供的数据
        return <div>{this.context}</div>
      }
    }
    

**实现二: **

export default class Demo extends Component {
    // 给要使用的Context的Demo类,添加静态contextType属性, 并赋值为context对象
    // 那么在Demo的实例对象上context属性中就可以获取到需要的值
  static contextType = context
  render() {
    return <div>{this.context}</div>
  }
}

应用: 利用 context 改造 todos 案例

pubsub

兄弟/任意组件间直接通信

发布订阅机制: publish / subscribe
pubsub-js是一个用JS编写的库。
利用订阅发布模式, 当一个组件的状态发生了变化,可以通知其他任意组件更新这些变化
在这里插入图片描述

实现:

  • 安装

    npm install pubsub-js / yarn add pubsub-js
    
  • 导入

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

    // 订阅消息
    // 参数一: 消息名
    // 参数二: 用于接收数据的函数
    // token 订阅消息返回的令牌(用于取消订阅)
    const token = PubSub.subscribe('消息名', function (msg, data) {
        console.log( msg, data );
    });
    
    // 发布消息
    // 参数一: 消息名
    // 参数二: 要传递的数据
    PubSub.publish('消息名', 'hello world!');
    
    
    // 取消指定的订阅
    PubSub.unsubscribe(token);
    
    // 取消某个话题的所有订阅
    PubSub.unsubscribe(消息名);
    
    /*
    div.addEventListener('click', (event) => {})
    我们点击div => 浏览器自动帮我分发事件: 事件名, 包含事件相关数据的事件对象
    div.removeEventListener('click')
    */
    

利用 pubsub 改造 users 案例

Fragment

doucmentFragment: 是原生DOM中, 内存中可以用来保存多个DOM节点对象的容器

如果将这个fragment添加到页面中, 它本身不会进入页面, 它的所有子节点会进行页面

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

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

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

测试DocumentFragment

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>测试DocumentFragment</title>
</head>
<body>
  <div id="test"></div>

  <script>
    const testDiv = document.getElementById('test')

    const h1 = document.createElement('h1')
    h1.innerHTML = '我是标题'
    const p = document.createElement('p')
    p.innerHTML = '我是内容'

    const fragment = document.createDocumentFragment()
    fragment.appendChild(h1)
    fragment.appendChild(p)

    testDiv.appendChild(fragment)

  </script>
</body>
</html>

不使用React.Fragment

function Hello(){
    return (
      // 渲染到页面之后,这个div就是一个多余的
      <div>
        <h1>fragment</h1>
        <p>hello react</p>
      </div>
    ) 
}

使用React.Fragment


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>
      </>
    ) 
}

其他 hooks

useContext

在hook组件函数中读取context的Provide提供的数数据

function Child() {
  const data = useContext(context)
  return (
    <div>{data}</div>
  )
}

官方 Hooks

利用 Hooks 语法改造 todos 和 users 案例

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>
      </>
    ) 
}

其他 hooks

useContext

在hook组件函数中读取context的Provide提供的数数据

function Child() {
  const data = useContext(context)
  return (
    <div>{data}</div>
  )
}

官方 Hooks

利用 Hooks 语法改造 todos 和 users 案例

吐槽:csdn居然不支持jsx。。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值