前端学习--React部分

文章目录

前端学习--React部分

前言

  • 在学习React之前需要掌握的JavaScript基础知识有class(类)、ES6语法规范、npm包管理器、原型、原型链、数组的常用方法、模块化。如果上述知识点你有些淡忘,请前往我的博客主页进行查看。

  • 在讲解过程中,老师会复习旧的知识点。请前往我的gitee链接中进行查看全部课程的代码:

    https://gitee.com/marygood/tkreactstudy

  • 下载文件去这里:https://www.bootcdn.cn/(下载的时候看好文件名)

1.React简介

官网给出的定义:用于构建用户界面的JavaScript库(只关注页面)。

通俗定义:将数据渲染为HTML视图的开源JavaScript库。

学习React的原因:

  • 原生JS操作DOM繁琐且效率低,因为用DOM-API操作UI

  • JS直接操作DOM会使浏览器进行大量的重绘重排

  • 原生JS没有组件化编码方案,代码复用率低

1.1React的特点

  • 采用组件化模式,声明式编码,提高开发效率及组件复用率

  • 在React Native中可以使用React语法进行移动端开发

  • 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互

    注:最后一点很重要,它是React高效的原因。

    问:为什么使用虚拟DOM?因为操作页面的代价比操作js大得多。

    实现流程:把数据交给React,它便会拿着这些数据生成虚拟DOM,之后将虚拟DOM转换为真实DOM,最后将这些呈现在页面上。

    补充:数据放在状态(state)中,不是随便放置的。

1.2引入文件

1.react.js (核心库):核心库要在react-dom之前引入

2.react-dom.js :提供操作DOM的react扩展库

3.babel.min.js:解析JSX语法代码转为JS代码的库,即ES6==>ES5;JSX==>JS

需要注意的是引入文件一定要按照这个顺序。

1.3JSX

🍉JSX简介与使用

全称 JavaScript XML ,是react定义的一种类似于XML的JS扩展语法,本质是React.createElement(component, props, ...children)方法的语法糖。JXS最终产生的虚拟DOM就是一个JS对象。

详细代码请前往码云链接。

🍉JSX语法规则

1.定义虚拟DOM时,不要写引号

2.标签中混入js表达式时用{}

3.样式的类名指定不要用class,要用className

4.内联样式,要用style={{key:value}}的形式去写

5.只有一个根标签

6.标签必须闭合

7.标签首字母

​ (1)小写默认为html标签:<good></good> //不是html标签--报错

​ (2)大写默认为组件:<Good></Good> //未定义组件--报错

1.4模块与组件

🍉模块
  • 向外界提供特定功能的js程序。随着业务逻辑增加,代码越来越多且复杂,此时js一般会拆成多个js文件来编写,一般一个js文件就是一个模块
  • 作用:复用js,简化js的编写,提高js的运行效率
  • 模块化:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
🍉组件
  • 用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)。比如一个功能齐全的Header栏就是一个组件。
  • 复用编码, 简化项目编码, 提高运行效率
  • 组件化:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

1.5安装开发者工具

我自己直接在谷歌应用商店下载的,现在的发布者已经变为Meta。

打开扩展程序,找到安装好的React Developer Tool,点击详情,并勾选上以下内容。

2.React面向组件编程

2.1创建组件

🍉函数式组件

简单组件,没有state,本身没有三大属性,不过通过hooks也有了三大属性。

🍉类式组件

复杂组件,有state,有三大属性。

2.2组件实例的三大属性

🍉state属性
  • state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
  • 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

代码实例:切换天气状态(简写版)

html 复制代码
<script type="text/babel">
    // 实际开发中一定是写精简版本的
    // 1.创建组件
    class Weather extends React.Component{
      // 初始化状态
      state={isHot:false,wind:"微风"}
      render() {
        // 读取状态(解构赋值)
        const {isHot,wind} =this.state
        // 注意此处React事件绑定的写法
        return <h1 onClick={this.changeWeather}>今天天气{isHot ? '热':'凉'},{wind}</h1>
      }
      // 和完整写法比这里用了箭头函数,就不再使用构造器
      changeWeather=() => {
        // 获取原来的isHot值
        const isHot=this.state.isHot
        // 注意:状态(不可以直接更改)必须通过setState进行更新,且更新是一种合并,不是替换。
        // this.state.isHot=!isHot //错误写法
        this.setState({isHot:!isHot})
      }
    }
    // 2.渲染组件到页面
    ReactDOM.render(<Weather/>,document.getElementById('test'))
  </script>

这里需要注意的是在render函数中创建虚拟DOM时,直接在标签中绑定事件,且事件写法不同于原生JS,如原生JS中的onclick事件,在react中要写成onClick,其他同理。其次,需要注意的是状态数据不能直接修改或更新,要使用setState。

🍉props属性
  • 通过标签属性(创建虚拟DOM时直接添加的数据)从组件外向组件内传递变化的数据

  • 传递props,即传递标签属性 -- props批量传递标签属性

  • 组件标签的所有属性都保存在props中

  • props是只读的,不要在组件内部修改props

  • 如果想对标签属性进行类型、必要性限制,需要引入prop-types库(不是必须的,看情况)

代码实例: 自定义用来显示人员信息

标准形式 -- 写在外部

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>hello_react</title>
</head>
<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>
  <div id="test1"></div>
  <div id="test2"></div>

  <!-- 引入依赖 ,引入的时候,必须就按照这个步骤-->
  <!-- 引入react核心库 -->
  <script src="../js/react.development.js" type="text/javascript"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script src="../js/react-dom.development.js" type="text/javascript"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script src="../js/babel.min.js" type="text/javascript"></script>
  <!--引入对于组件标签的限制-->
  <script src="../js/prop-types.js"></script>

  <!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    // 1.创建组件
    class Person extends React.Component{
      render() {
        console.log(this)
        const {name,age,sex} = this.props
        return(
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{sex}</li>
            <li>年龄:{age+1}</li>
          </ul>
        )
      }
    }

    // 对标签属性进行类型、必要性限制
    Person.propTypes={
      name:PropTypes.string.isRequired, //限制name必传,且为字符串
      sex:PropTypes.string,//限制sex为字符串
      age:PropTypes.number,//限制age为数值
      speak:PropTypes.func,//现在speak为函数
    }

    // 指定默认标签属性值
    Person.defaultProps={
      sex:'男',
      age:18
    }

    // 2.渲染组件到页面
    ReactDOM.render(<Person name="Bob" age={19} speak={speak}/>,document.getElementById("test"))
    ReactDOM.render(<Person name="Tom" sex="男"/>,document.getElementById("test1"))
    // 另一种 写法
    const p={name:"Lily",age:30,sex:"女"}
    ReactDOM.render(<Person {...p}/>,document.getElementById('test2'))

    function speak() {
      console.log('我说话了')
    }
  </script>
</body>
</html>

简写形式 -- 写在内部

html 复制代码
  <!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    // 1.创建组件
    class Person extends React.Component{
      // 对标签属性进行类型、必要性限制
    static propTypes={
      name:PropTypes.string.isRequired, //限制name必传,且为字符串
      sex:PropTypes.string,//限制sex为字符串
      age:PropTypes.number,//限制age为数值
    }

    // 指定默认标签属性值
    static defaultProps={
      sex:'男',
      age:18
    }

      render() {
        console.log(this)
        const {name,age,sex} = this.props
        return(
          <ul>
            <li>姓名:{name}</li>
            <li>性别:{sex}</li>
            <li>年龄:{age+1}</li>
          </ul>
        )
      }
    }

    // 2.渲染组件到页面
    ReactDOM.render(<Person name="Bob"/>,document.getElementById("test"))
  </script>

注意:

  • 首字母小写的propTypes是类里的属性规则
  • 首字母大写的PropTypes是prop-types库里的内置对象

函数组件使用props

html 复制代码
  <!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    // 注意:到目前学习阶段,函数组件只能使用三大属性中props
    function Person(props){
      const {name,age,sex} = props
      return(
        <ul>
          <li>姓名:{name}</li>
          <li>性别:{sex}</li>
          <li>年龄:{age}</li>
        </ul>
      )
    }
   // 渲染组件到页面
   ReactDOM.render(<Person name='Bob' sex='male' age='12'/>,document.getElementById('test'))
  </script>
🍉refs属性

组件内的标签可以定义ref属性来标识自己。this.refs可以拿到真实DOM。

1.字符形式的ref
html 复制代码
  <!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
        // 展示左侧输入框数据
        showData = () =>{
          const {input1}=this.refs
          alert(input1.value)
          // console.log(this)
        }
        // 展示右侧输入框的数据
        showDataRight=()=>{
          const {input2}=this.refs
          alert(input2.value)
        }
        render(){
            return (
                <div>
                  <input ref="input1" type="text" placeholder="点击弹出提示数据" />&nbsp;
                  <button  onClick = {this.showData}>点击</button>&nbsp;
                  <input ref="input2" onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" />
                </div>
            )
        }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))

    /* 
      ref与refs介绍:
        1.只要有ref标识就会有refs属性(有ref就不用id了,功能类似)
        2.这节使用的字符串形式的ref比较老旧,不推荐使用,因为效率低
    */
  </script>
2.回调函数形式的ref

回调函数:一种特殊的函数。它会作为参数传递给另一个函数,并在该函数被调用执行完毕后执行。

html 复制代码
<script type="text/babel">
    class Demo extends React.Component{
        // 展示左侧输入框数据
        showData = () =>{
          const {input1}=this
          alert(input1.value)
        }
        // 展示右侧输入框的数据
        showDataRight=()=>{
          const {input2}=this
          alert(input2.value)
        } 
        // currentNode当前节点,两种写法,一种是完整版,一种是精简版
        render(){
            return (
                <div>
                  <input ref={(currentNode)=>{this.input1=currentNode}} type="text" placeholder="点击弹出提示数据" />&nbsp;
                  <button  onClick = {this.showData}>点击</button>&nbsp;
                  <input ref={ c => this.input2=c } onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" />
                </div>
            )
        }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
  </script>
3.使用createRef(react最推荐的形式)
html 复制代码
  <!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
        // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器的使用是需要一一对应的
        myRef=React.createRef()
        // myRefRight=React.createRef()
        // 展示左侧输入框数据
        showData = () =>{
          alert(this.myRef.current.value)
        }
        // 展示右侧输入框的数据
        showDataRight=()=>{
          alert(this.myRefRight.current.value)
        } 
        // currentNode当前节点,两种写法,一种是完整版,一种是精简版
        render(){
            return (
                <div>
                  <input ref={this.myRef} type="text" placeholder="点击弹出提示数据" />&nbsp;
                  <button onClick = {this.showData}>点击</button>&nbsp;
                  <input ref={this.myRefRight=React.createRef()} onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" />
                </div>
            )
        }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))

    /* createRef(API)这种方式,是目前react最推荐的。 */

2.3事件处理

html 复制代码
<!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
      /* 
        1.通过onXxx属性指定事件处理函数(注意大小写)
          a.React使用的是自定义(合成)事件,而不是使用原生DOM事件 -- 为更好的兼容性
          b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)-- 为高效
        2.通过event.target得到发生事件的DOM元素对象 -- 不要过度使用ref(有事件就不用ref)
      */
        // 创建ref容器
        myRef=React.createRef()
        // 展示左侧输入框数据
        showData = () =>{
          alert(this.myRef.current.value)
        }
        // 展示右侧输入框的数据
        showDataRight=(event)=>{
          alert(event.target.value)
        } 
        // currentNode当前节点,两种写法,一种是完整版,一种是精简版
        render(){
            return (
                <div>
                  <input ref={this.myRef} type="text" placeholder="点击弹出提示数据" />&nbsp;
                  <button onClick = {this.showData}>点击</button>&nbsp;
                  <input onBlur={this.showDataRight} type="text" placeholder="失去焦点弹出提示数据" />
                </div>
            )
        }
    }

    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
  </script>

2.4 受控组件和非受控组件

🍉非受控组件
html 复制代码
<!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
        login = (event) =>{
          event.preventDefault() //阻止表单提交
          const {saveName,savePwd}=this
          alert(`姓名:${saveName.current.value},密码:${savePwd.current.value}`)
        }
        render(){
            return (
              <form action="http://www.baidu.com" onSubmit={this.login}>
                用户名:<input ref={this.saveName=React.createRef()} type = "text" name ="username"/>
                密码<input ref={this.savePwd=React.createRef()} type = "password" name ="password"/>
                <button>登录</button>
              </form>
            )
        }
    }
    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
    /* 现用现取,就属于非受控组件 */
  </script>
🍉受控组件
html 复制代码
<!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
        saveName = (event) =>{
          this.setState({username:event.target.value})
        }  
        savePwd=(event)=>{
          this.setState({password:event.target.value})
        }
        login = (event) =>{
          event.preventDefault() //阻止表单提交
          const {username,password} = this.state //注意:username和password是值
          alert(`姓名:${username},密码:${password}`)
        }
        
        render(){
            return (
              <form action="http://www.baidu.com" onSubmit={this.login}>
                用户名:<input onChange={this.saveName} type = "text" name ="username"/>
                密码<input onChange={this.savePwd} type = "password" name ="password"/>
                <button>登录</button>
              </form>
            )
        }
    }
    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
    /* 
        随着输入维护状态,就属于受控组件。(类似vue中的双向数据绑定)
        通俗理解:通过ref就是非受控,通过change就是受控。
        建议写受控组件
    */
  </script>

2.5高阶函数与函数柯里化

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>hello_react</title>
</head>
<body>
  <!-- 准备好一个容器 -->
  <div id="test"></div>

  <!-- 引入依赖 ,引入的时候,必须就按照这个步骤-->
  <!-- 引入react核心库 -->
  <script src="../js/react.development.js" type="text/javascript"></script>
  <!-- 引入react-dom,用于支持react操作DOM -->
  <script src="../js/react-dom.development.js" type="text/javascript"></script>
  <!-- 引入babel,用于将jsx转为js -->
  <script src="../js/babel.min.js" type="text/javascript"></script>
  <!--引入对于组件标签的限制-->
  <script src="../js/prop-types.js"></script>

  <!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
        // saveFormData就是高阶函数
        saveFormData = (dataType) =>{
          return(event)=>{
            // 调用变量形式的对象属性名
            // 可以理解为加上中括号的dataType就成为了一个属性
            this.setState({[dataType]:event.target.value})
          }
        }  
        
        login = (event) =>{
          event.preventDefault() //阻止表单提交
          const {username,password} = this.state //注意:username和password是值
          alert(`姓名:${username},密码:${password}`)
        }
        
        // 能保证onChange等于的是一个函数即可,无需过度在意加不加括号
        render(){
            return (
              <form action="http://www.baidu.com" onSubmit={this.login}>
                用户名:<input onChange={this.saveFormData('username')} type = "text" name ="username"/>
                密码<input onChange={this.saveFormData('password')} type = "password" name ="password"/>
                <button>登录</button>
              </form>
            )
        }
    }
    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
    /* 
      高阶函数:如果一个函数符合下面2个规范中的任何一个,那么该函数就是高阶函数。
          1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
          2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
          常见的高阶函数有:Promise、setTimeout、arr.map()等等

      函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
    */
  </script>
</body>
</html>

不过也不是一定要用函数的柯里化实现,下面介绍不用函数柯里化实现的方法:

html 复制代码
<!-- 此处一定要写babel,不写会默认为js,将报错 -->
  <script type="text/babel">
    class Demo extends React.Component{
        // saveFormData就是高阶函数
        saveFormData = (dataType,event) =>{
            this.setState({[dataType]:event.target.value})
        }  
        
        login = (event) =>{
          event.preventDefault() //阻止表单提交
          const {username,password} = this.state //注意:username和password是值
          alert(`姓名:${username},密码:${password}`)
        }
        
        // 能保证onChange等于的是一个函数即可,无需过度在意加不加括号
        render(){
            return (
              <form action="http://www.baidu.com" onSubmit={this.login}>
                用户名:<input onChange={event => this.saveFormData('username',event)} type = "text" name ="username"/>
                密码<input onChange={event => this.saveFormData('password',event)} type = "password" name ="password"/>
                <button>登录</button>
              </form>
            )
        }
    }
    // 渲染组件到页面
    ReactDOM.render(<Demo/>,document.getElementById('test'))
  </script>

2.6生命周期

🍉引入
  1. React组件中包含一系列生命周期回调函数。我们在定义组件时,会在特定的生命周期回调函数中做特定的工作。
  2. 生命周期回调函数也可以称为:生命周期钩子函数 == 生命周期函数 ==生命周期钩子。

补充react写法

  1. 行内样式写法:style={``{opacity:opacity}}
  2. 对象中属性名和属性值相同时触发简写:{opacity:opacity}简写为{opacity}
🍉react生命周期(旧)
1.初始化阶段: 由ReactDOM.render()触发---初次渲染
    1)constructor()
    2)componentWillMount()
    3)render(): 常用,初始化或者更新渲染
    4)componentDidMount(): 常用,一般在这个钩子中做一些初始化的事,如开启定时器、发送网络请求、订阅消息
2.更新阶段: 由组件内部this.setSate()或父组件重新render触发
    1)shouldComponentUpdate() -- 闸门,返回值是布尔值,为true时执行后续代码
    2)componentWillUpdate()
    3)render()
    4)componentDidUpdate()
3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    1)componentWillUnmount() :常用,一般在这个钩子中做一些收尾的事,如关闭定时器、取消订阅消息
🍉react生命周期(新)
1.初始化阶段: 由ReactDOM.render()触发---初次渲染
    1)constructor()
    2)getDerivedStateFromProps
    3)render(): 常用,初始化或者更新渲染
    4)componentDidMount(): 常用,一般在这个钩子中做一些初始化的事,如开启定时器、发送网络请求、订阅消息
2.更新阶段: 由组件内部this.setSate()或父组件重新render触发
    1)getDerivedStateFromProps
    2)shouldComponentUpdate()
    3)render()
    4)getSnapshotBeforeUpdate --
    5)componentDidUpdate()
3.卸载组件: 由ReactDOM.unmountComponentAtNode()触发
    1)componentWillUnmount() :常用,一般在这个钩子中做一些收尾的事,如关闭定时器、取消订阅消息
注:新出的也不常用    	

2.7虚拟DOM与DOM Diffing算法

经典面试题:
1)react/vue中的key有什么作用?(key的内部原理是什么?)
2)为什么遍历列表时,key最好不要用index?

1.虚拟DOM中key的作用:
    1)简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
    2)详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后react进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
        a.旧虚拟DOM中找到了与新虚拟DOM相同的key:
            (1) 若虚拟DOM中内容没变, 直接使用之前的真实DOM
            (2) 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
        b.旧虚拟DOM中未找到与新虚拟DOM相同的key:根据数据创建新的真实DOM,随后渲染到到页面
2.用index作为key可能会引发的问题:
    1)若对数据进行:逆序添加、逆序删除等破坏顺序操作:
    会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
    2)如果结构中还包含输入类的DOM(如input,虚拟DOM中标签属性少,input没有value属性):
    会产生错误DOM更新 ==> 界面有问题。
    3)注意! 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3.开发中如何选择key?
    1)最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
    2)如果确定只是简单的展示数据,用index也是可以的。

3.react脚手架

3.1创建与启动

  1. 全局安装:npm i -g create-react-app
  2. 切换到想要创建项目的目录,使用命令:create-react-app hello-react。(hello-react是文件名)
  3. 用VSCode打开文件,并在终端(软件内)启动项目:npm start

3.2react脚手架项目结构

🍉默认文件
public -- 静态资源文件夹
favicon.icon -- 网站页签图标
index.html -- 主页面
logo192.png -- logo图
logo512.png -- logo图
manifest.json -- 应用加壳的配置文件
robots.txt -- 爬虫协议文件

src -- 源码文件夹
App.css -- App组件的样式,App.js里引用
App.js -- App组件,子组件可以都写在components文件夹里,子组件文件夹首字母大写。路由组件写在pages文件夹
App.test.js -- 用于给App做测试
index.css -- 样式,通用样式,入口文件里引用
index.js -- 入口文件 
logo.svg -- logo图 
reportWebVitals.js -- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js -- 组件单元测试的文件(需要jest-dom库的支持)
🍉编码流程
拆分组件: 拆分界面,抽取组件
实现静态组件: 使用组件实现静态页面效果
实现动态组件:
    3.1 动态显示初始化数据
          3.1.1 数据类型
          3.1.2 数据名称
          3.1.3 保存在哪个组件?
    3.2 交互(从绑定事件监听开始)

3.3TodoList案例

详细代码见前言中的gitee链接。

🍉todoList案例提要
  1. 写法的注意:class -- classNamestyle="" -- style={``{}}
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中?
    1)某个组件使用:放在其自身的state
    2)某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信:
    1)【父组件】给【子组件】传递数据:通过props传递
    2)【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultCheckedchecked的区别,类似的还有:defaultValuevalue
  5. 状态在哪里,操作状态的方法就在哪里

4.React ajax

4.1简介

  1. React本身只关注于界面, 并不包含发送ajax请求的代码
  2. 前端应用需要通过ajax请求与后台进行交互(json数据)
  3. react应用中需要集成第三方ajax库(或自己封装,但是基本不会自己封装)

4.2实现

  1. jQuery: 要操作DOM,并且比较重, 如果需要另外引入不建议使用
  2. axios: 轻量级, 建议使用
    1)封装XmlHttpRequest对象的ajax
    2)promise风格
    3)可以用在浏览器端和node服务器端

4.3配置代理

方法一

在package.json中追加配置:"proxy":"http://localhost:5000"

  • 优点:配置简单,前端请求资源时可以不加任何前缀。
  • 缺点:不能配置多个代理。
  • 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

方法二

  1. 创建代理配置文件:在src文件夹下创建setupProxy.js文件。

  2. 编写setupProxy.js配置具体代理规则:

    //这个文件名是固定的,不可改
    const proxy = require('http-proxy-middleware')
    
    module.exports = function(app) {
      app.use(
        proxy('/api1', {  //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
          target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址)
          changeOrigin: true, //控制服务器接收到的请求头中host字段的值(可以欺骗一下浏览器)
          /*
          	changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
          	changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000
          	changeOrigin默认值为false,但我们一般将changeOrigin值设为true
          */
          pathRewrite: {'^/api1': ''} //去除请求前缀/api1,保证交给后台服务器的是正常请求地址(必须配置)
        }),
        proxy('/api2', { 
          target: 'http://localhost:5001',
          changeOrigin: true,
          pathRewrite: {'^/api2': ''}
        })
      )
    }
    
  3. 说明:

    • 优点:可以配置多个代理,可以灵活的控制请求是否走代理。

    • 缺点:配置繁琐,前端请求资源时必须加前缀。

  4. react 18版本写法:在后续的github案例中我自己用的这个。

    //react 18版本写法
    const {createProxyMiddleware} = require('http-proxy-middleware')
                      
    module.exports = function (app) {
        app.use(
            createProxyMiddleware('/api1', {
                target: 'http://localhost:5000',
                changeOrigin: true,
                pathRewrite: {'^/api1': ''}
            })
        )
    }
    

4.4github搜索案例

详细代码在前言的gitee链接中。

🍉axios版本提要
  • 连续解构赋值+重命名:

    javascript 复制代码
    const {keyWordElement:{value:keyWord}} = this
  • 三元表达式:

    javascript 复制代码
    isFirst ? <h2>欢迎使用,输入关键字,随后点击搜索</h2> :
    isLoading ? <h2>Loading......</h2> :
    err ? <h2 style={{color:'red'}}>{err}</h2> 
  • 如果你说什么都展现不出效果:

    javascript 复制代码
    //App.jsx文件
    export default class App extends Component{
    	...
        updateAppState = (stateObj) =>{
                this.setState({...stateObj})
            }
        ...
    }
🍉pubsub版本提要
工具库: PubSubJS
下载: npm install pubsub-js --save
使用:
1) import PubSub from 'pubsub-js' //引入
2) PubSub.subscribe('delete', function(data){ }); //订阅
3) PubSub.publish('delete', data) //发布
4) 要在组件的componentWilUnmount中取消订阅
注:谁用谁订阅,谁有订阅者所需的数据谁就发布。
🍉Fetch版本提要(了解)
发送ajax请求的方法:
    原生AJAX:xhr
    jQuery:对xhr的封装
    axios:对xhr的封装
    fetch:不是库,是windows内置,且是Promise风格。
注:有些文章拉踩,不过还是要以主流为主。
fetch的特点:关注分离,兼容性不高,老版本浏览器不支持。

5.React路由

5.1相关理解

🍉对SPA应用的理解
  1. 单页Web应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面。
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  4. 数据都需要通过ajax请求获取, 并在前端异步展现。
🍉路由的理解
  • 什么是路由?

    1. 一个路由就是一个映射关系(key:value)
    2. key为路径, value可能是function或component
  • 路由的分类

    1.后端路由:

    • 理解:value是function, 用来处理客户端提交的请求。
    • 注册路由: router.get(path, function(req, res))
    • 工作过程:当node接受到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据。

    2.前端路由:

    • 浏览器端路由,value是component,用于展示页面内容。
    • 注册路由: <Route path="/test" component={Test}>
    • 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件。
🍉react-router的理解
  1. react的一个插件库。

    • 安装:npm i react-router-dom@5。(注意是5版本)
  2. 专门用来实现一个SPA应用。

  3. 基于react的项目基本都会用到此库。

5.2路由的基本使用

  1. 明确好界面中的导航区、展示区
  2. 导航区的a标签改为Link标签 <Link to="/xxxxx" >Demo</Link>
  3. 展示区写Route标签进行路径的匹配<Route path='/xxxx 'component={Demo}/>
  4. <App>的最外侧包裹了一个<BrowserRouter><HashRouter>
  5. 注意:详细代码见gitee。

5.3路由组件与一般组件

  1. 写法不同

    • 一般组件:<Demo/ >
    • 路由组件: <Route path="/demo" component={Demo}/>
  2. 存放文件位置不同:

    • 一般组件:在components文件中
    • 路由组件:在pages文件中
  3. 接收到的props不同:

    • 一般组件:写组件标签时传递了什么,就能收到什么

    • 路由组件:接收到三个固定的属性

      1. history:

        go: f go(n)
        goBack: f goBack()
        goForward: f goForward()
        push: f push(path,state)
        replace: f replace(path, state)
        
      2. location:

        pathname: "/about"
        search: ""
        state: undefined
        
      3. match:

        params: {}
        path: "/about"
        url: "/about"
        
  1. NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
  2. 标签体内容是一个特殊的标签属性
  3. 通过this.props.children可以获取标签体内容
  4. 注意:详细代码见gitee。

5.5Switch的使用

  1. 通常情况下,path和component是一一对应的关系。
  2. Switch可以提高路由匹配效率(单一匹配)。
  3. 详细代码见gitee。

5.6解决样式丢失问题

  1. public/index.html 中引入样式时不写./写/(常用)

    javascript 复制代码
    <link rel="stylesheet" href="css/bootstrap.css">
  2. public/index.html 中引入样式时不写﹒/写%PUBLIC_URL%(常用)

    javascript 复制代码
    <link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
  3. 使用HashRouter (不常用)

    react 复制代码
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    import {BrowserRouter, HashRouter} from "react-router-dom";
              
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
        <HashRouter>
            <App/>
        </HashRouter>
    );

5.7路由的模糊匹配与严格匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
  2. 开启严格匹配:<Route exact={true} path="/about" component={About}/>
  3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

5.8Redirect的使用

  1. 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由。

  2. 具体编码:

    react 复制代码
    <Switch>
        <Route path="/about" component={About}/>
        <Route path="/home" component={Home}/>
        <Redirect to="/about"/>
    </Switch>

5.9嵌套路由

  1. 注册子路由时要写上父路由的path值
  2. 路由的匹配是按照注册路由的顺序进行的
  3. 注意:详细代码见gitee

5.10向路由组件传递参数

🍉向路由组件传递params参数
  • 路由链接(携带参数):<Link to='/demo/test/tom/18'}>详情</Link>

  • 注册路由(声明接收):<Route path="/demo/test/:name/:age" component={Test}/>

  • 接收参数:const {id,title} = this.props.match.params

🍉向路由组件传递search参数
  • 路由链接(携带参数):<Link to=' /demo/test?name=tom&age=18'}>详情</Link>

  • 注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>

  • 接收参数:const isearch} = this.props.location

  • 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

🍉向路由组件传递state参数
  • 路由链接(携带参数):

    react 复制代码
    <Link to={{path:' /demo/test',state:{name : ' tom' ,age:18]}}>详情</Link>注册路由(无需声明,正常注册即可):<Route path="/demo/test" component={Test}/>
  • 接收参数:this.props.location.state

  • 备注:刷新也可以保留住参数

5.11push与repalce

  • push:留下记录
  • repalce:不留记录
  • 注意:详细代码见gitee

5.12编程式路由导航

  • 借助this.prosp.history对象上的API对操作路由跳转、前进、后退

    this.prosp.history.push()
    this.prosp.history.replace()
    this.prosp.history.goBack()
    this.prosp.history.goForward()
    this.prosp.history.go()
    
  • 注意:详细代码见gitee

5.13withRouter的使用

  1. withRouter可以加工一般组件,让一般组件具备路由组件所特有的API
  2. withRouter的返回值是一个新组件
react 复制代码
import React, {Component} from 'react';
import {withRouter} from "react-router-dom";
 
class Header extends Component {
    back = () => {
        this.props.history.goBack()
    }
    forward = () => {
        this.props.history.goForward()
    }
    go = () => {
        this.props.history.go(2)
    }
 
    render() {
        // console.log('Header组件收到的props是', this.props)
        return (
            <div>
                <div className="page-header">
                    <h2>React Router Demo</h2>
                    <button onClick={this.back}>回退</button>
                    <button onClick={this.forward}>前进</button>
                    <button onClick={this.go}>go</button>
                </div>
            </div>
        );
    }
}
 
export default withRouter(Header);

5.14BrowserRouter与HashRouter

  1. 底层原理不一样:
    (1) BrowserRouter使用的是H5的history API,不兼容IE9及以下版本。
    (2) HashRouter使用的是URL的哈希值
  2. path表现形式不一样
    (1) BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
    (2) HashRouter的路径包含#,例如: localhost:3000/#/demo/test
  3. 刷新后对路由state参数的影响
    (1) BrowserRouter没有任何影响,因为state保存在history对象中。
    (2) HashRouter刷新后会导致路由state参数的丢失。
  4. 备注: HashRouter可以用于解决一些路径错误相关的问题。
  5. 总的来讲BrowserRouter常用。

6.React UI组件库

6.1流行的开源React UI组件库

🍉material-ui(国外)
  1. 官网: http://www.material-ui.com/#/

  2. Github:https://github.com/callemall/material-ui

🍉ant-design(国内蚂蚁金服)
  1. 官网: https://ant.design/index-cn

  2. Github: https://github.com/ant-design/ant-design/

7.Redux

7.1Redux简介

  • 什么是Redux?
  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件共享的状态。
  • 什么情况下需要使用redux?
  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

7.2Redux的工作流程

🍉redux原理图
🍉redux的三个核心概念
  • action
  1. 动作的对象
  2. 包含2个属性
    1. type:标识属性, 值为字符串, 唯一, 必要属性
    2. data:数据属性, 值类型任意, 可选属性
  3. 例子:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
  • reducer
  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数。
  • store
  1. 将state、action、reducer联系在一起的对象
  2. 如何得到此对象?
    1. import {createStore} from 'redux'
    2. import reducer from './reducers'
    3. const store = createStore(reducer)
  3. 此对象的功能?
    1. getState(): 得到state
    2. dispatch(action): 分发action, 触发reducer调用, 产生新的state
    3. subscribe(listener): 注册监听, 当产生了新的state时, 自动调用

7.3求和案例

🍉纯react版

详细代码见gitee。

🍉redux精简版
  • store.js:
    • 引入redux中的createStore函数,创建一个store
    • createstore调用时要传入一个为其服务的reducer
    • 记得暴露store对象
  • count_reducer.js:
    • reducer的本质是一个函数,接收: preState,action,返回加工后的状态
    • reducer有两个作用:初始化状态,加工状态
    • reducer被第一次调用时,是store自动触发的,传递的preState是undefined
  • 在index.js中检测store中状态的改变,一旦发生改变重新渲染<App/>
  • 备注: redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写。
  • 注意:详细代码见gitee
🍉redux完整版
  • 新增文件:
  1. count_action.js 专门用于创建action对象
  2. constant.js放置容易写错的type值
  • 注意:详细代码见gitee
🍉异步action版
  • 明确:延迟的动作不想交给组件自身,想交给action

  • 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。

  • 具体编码:

    • yarn add redux-thunk,并配置在store中
    • 创建action的函数不再返回一般对象,而是一个函数,该函数中写异步任务。
    • 异步任务有结果后,分发一个同步的action去真正操作数据。
  • 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步action

  • 注意:详细代码见gitee

7.4初识react-redux

🍉对react-redux的理解
🍉连接容器组件与UI组件,react-redux基本使用
  • 明确两个概念:

    • UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
    • 容器组件:负责和redux通信,将结果交给UT组件。
  • 如何创建一个容器组件-----靠react-redux 的connect函数

    • connect(mapStateToProps,mapDispatchToProps)(UI组件)
    • mapstateToProps:映射状态,返回值是一个对象
    • mapDispatchToProps:映射操作状态的方法,返回值是一个对象
  • 备注1:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入

  • 备注2:mapDispatchToProps也可以是一个对象

  • 注意:详细代码见gitee

7.5react-redux的优化

  • 简写mapDispatch

  • Provider组件的使用

  • 整合UI组件与容器组件

    • 容器组件和UI组件组合成一个文件

    • 无需自己给容器组件传递store,给<App/>包裹一个<Provider store={store}>即可。

    • 使用了react-redux后也不用再自己检测redux中状态的改变了,容器组件可以自动完成这个工作。

    • mapDispatchToProps也可以简单的写成一个对象

    • 一个组件要和redux"打交道"要经过哪几步?

      • 定义好UI组件---不暴露

      • 引入connect生成一个容器组件,并暴露,写法如下:

        react 复制代码
        connect(
        state =>({key:value})
        {key : xxxxxAction}
        )(UI组件)
    • 在UI组件中通过this.props.xxxxxxx读取和操作状态

  • 注意:详细代码见gitee

7.6数据共享

  • 定义一个Pserson组件,和Count组件通过redux共享数据

  • 为Person组件编写: reducer、action,配置constant常量。

  • 重点: Person的reducer和Count的Reducer要使用combineReducers进行合并,合并后的总状态是一个对象!!!

  • 交给store的是总reducer,最后注意在组件中取出状态的时候,记得"取到位"。

  • 注意:详细代码见gitee

7.7纯函数

  • 一类特别的函数:只要是同样的输入(实参),必定得到同样的输出(返回)
  • 必须遵守以下一些约束
    • 不得改写参数数据
    • 不会产生任何副作用,例如网络请求,输入和输出设备
    • 不能调用Date.now)或者Math.random()等不纯的方法
  • redux的reducer函数必须是一个纯函数

7.8redux开发者工具

  • 首先在Google浏览器中下载扩展程序Redux DevTools

  • 安装:终端输入npm i redux-devtools-extension

  • store中进行配置

    react 复制代码
    import {composewithDevTools} from 'redux-devtools-extension'
    const store =createStore(allReducer,composelithDevTools(applyMiddleware(thunk)))

7.9最终版

  • 所有变量名字要规范,尽量触发对象的简写形式。
  • reducers文件夹中,编写index.js专门用于汇总并暴露所有的reducer
  • 注意:详细代码见gitee

7.10项目打包

  • 打包:npm run build
  • 下载插件:npm i serve -g
  • 启动:serve build
  • 注意:在实际开发中,需要把build交给后端人员,再由后端人员配置到服务器中。
相关推荐
百万蹄蹄向前冲36 分钟前
2024不一样的VUE3期末考查
前端·javascript·程序员
轻口味1 小时前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami1 小时前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
虾球xz1 小时前
游戏引擎学习第55天
学习·游戏引擎
oneouto1 小时前
selenium学习笔记(二)
笔记·学习·selenium
sealaugh321 小时前
aws(学习笔记第十九课) 使用ECS和Fargate进行容器开发
笔记·学习·aws
wakangda2 小时前
React Native 集成原生Android功能
javascript·react native·react.js
吃杠碰小鸡2 小时前
lodash常用函数
前端·javascript
emoji1111112 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼2 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs