React入门


React——用于动态构建用户界面的 JavaScript 库(声明式编码、组件化模式)

React Native开发移动端原生应用

JSX—— JavaScript XML

1、基本使用:

  1.1:相关库

  1. react.js:React核心库。
  2. react-dom.js:提供操作DOM的react扩展库。
  3. babel.min.js:解析JSX语法代码转为JS代码的库。

  1.2:JS创建基本语法:React.createElement(component, props, ...children)

  1.3:JSX创建基本语法:直接写html结构即可,babel转义成js。其就是 js 创建语法的语法糖

  1.4:渲染基本语法:ReactDOM.render(virtualDOMcontainerDOM)

  1.5:语法规则:  1、定义虚拟DOM无引号;

          2、标签混入js表达式(不是js语句),用{};

            这里要区分一下什么是表达式什么是语句(代码):

              1、语句:if、for等,不产生值的代码,控制代码逻辑的

               2、表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,比如a、a+b、demo(1),其实就是等号右边

          3、标签类名用className,不用class,因为ES6有关键字class

          4、内联样式,要用{{ }},最外层相当于jsx的执行,如2,里层为对象,所以为双花括号

          5、JSX虚拟DOM只能有一个根标签

          6、JSX标签必须闭合,或者

          7、标签首字母:

            (1)若小写开头,则将该标签转为html中的同名元素;若无该标签,则报错

            (2)若大写,则认为是组件

  1.6:模块、组件、模块化与组件化

    模块(JS模块):向外提供特定功能的js程序, 一般就是一个js文件,复用js, 简化js的编写, 提高js运行效率。

    组件(大于模块,因为其包含JS,还有html、css、video等等):用来实现局部功能效果的代码和资源的集合(html/css/js/image等等),复用编码, 简化项目编码, 提高运行效率

    模块化的:当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

    组件化的:当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

2、面向组件编程(函数式、类式)

  函数式组件:

  

  类式组件:

  

3、组件实例的三大核心属性这里我们研究class,因为最初是只有class才有状态,才有this,才是复杂组件,才有这三大核心属性。新版react给函数式定义了hooks,暂且按下不表):

  1、state(因为学过vue,且都是数据驱动,这里完全可以看做是vue中的data,其也不能直接修改,需要用setState):

    值是对象(可以包含多个key-value的组合),通过更新组件的state来更新对应的页面显示(重新渲染组件)

    state的使用要注意一个点就是this的指向问题:函数的this,使用bind或者箭头函数解决

    学习版的react,根据顺序来捋清逻辑:

  

    稍微简化:

  

   2、props(也和Vue的props传输类似,外部传进组件内部的值)

    每个组件对象都会有props(properties的简写)属性,组件标签的所有属性都保存在props中

    组件内部不要修改props数据

  

    1、展开运算符...:一次传递多个属性,但是要注意传进去的属性名要和组件内的对齐。

   这里的展开运算符需要注意:展开运算符可以展开数组,但是不能直接展开一个对象,ES6中使用{...p}是表示深拷贝一个对象。但是这里的花括号没有意义,只是单纯的做一个分隔符,花括号内部是在执行JS,react和babel解析了...,所以可以使用展开运算符展开对象

    ReactDOM.render(,document.getElementById("test1"))
    const p = {"name":'tom' ,"gender":'男' ,"age":'18'}
    ReactDOM.render({...p}/>,document.getElementById("test2"))

    2、props类型限制:propTypes。其实有点像是TS了,对属性进行各种限制

    3、props默认值:defaultProps。也可以把下面的属性直接放进Person类里面

    Person.propTypes={  //P小写是因为react的限制,内部需要遍历到这个属性,才可以有限制
      name: PropTypes.string.isRequired,  //name必须有,是字符。这里的P大写是因为调用了库
      gender: PropTypes.string,  //gender可以没有,是字符
      speak: PropTypes.func  //speak限制为方法,为了不和关键字冲突,库默认方法属性叫func
    }
    Person.defaultProps={
      gender: '男',
      age: 16
    }
//直接写类里,创建属于类的属性或方法需要使用静态,可以不通过实例进行访问。这里要重点关注实例属性和类属性的区别
class Person extends React.Component{
     static propTypes={
      name: PropTypes.string.isRequired,  //name必须有,是字符
      gender: PropTypes.string,  //gender可以没有,是字符
      speak: PropTypes.func  //speak限制为方法,为了不和关键字冲突,库默认方法属性叫func
    }
    static defaultProps={
      gender: '男',
      age: 16
    }
}

    4、props是只读的,不可修改(和vue也一样了,防污染,不应该修改外面传进来的值)

    5、构造器:可以不写,因为要写的时候所考虑的情况我们都处理掉了。

        官网描述:通常,在 React 中,构造函数仅用于以下两种情况:

          1、通过给this.state赋值对象来初始化内部state。我们外部赋值了state

          2、为事件处理函数绑定实例。我们通过箭头函数解决了

      constructor(props){
        super(props)
        this.state = {isHot:true,wind:'大风'}  //已经外部赋值
        this.changeHot = this.changeHot.bind(this)  //箭头函数已解决this问题
      }

    6、函数式组件的props:一般来说函数因为在严格模式下没有所谓的this,所以就不会有state和refs,但因为可以传参,所以可以使用props(后面降到hooks可以用),其限制只能像上述的写在函数外,而不能写在函数内部

    function Person(props){
      return (
          
  • 姓名:{props.name}
  • 性别:{props.gender}
  • 年龄:{+props.age+1}
) } ReactDOM.render(,document.getElementById("test1"))

  3、refs:组件内的标签可以定义ref属性来标识自己

    1、基本形式:但是这个不推荐

  • 当 ref 定义为string 时,需要React实时追踪当前正在渲染的组件,在 reconciliation 阶段, React Element 创建和更新的过程中, ref 会被包装为一个闭包函数, 等待 commit 阶段被执行,这会对React 的性能产生一些影响。
  • 当使用 render callback 模式的时候,使用 string ref 会造成 ref 挂载位置产生歧义。
  • string ref 无法被组合,例如一个第三方库的父组件已经给子组件传递了 ref,那么我们就无法再在子组件上添加 ref 了,而 callback ref 可完美解决此问题。
    class Person extends React.Component{
      showData=()=>{
        // const input1 = document.getElementById('right')
        alert(this.refs.left.value)
      }
      render(){
        return (
          
ref='left'/>
) } } ReactDOM.render(,document.getElementById("test1"))

    2、回调函数形式:如果使用内联函数,组件更新的时候其回调函数会执行两次。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题。但是执行两次也并不影响

    class Person extends React.Component{
      state = {isHot:true}
      // 类绑定,更新时只会执行一次
      changeState=(c)=>{
        const {isHot} = this.state
        this.setState({isHot:!isHot})
        console.log(c);

      }
      showData=()=>{
        alert(this.left.value)
      }
      render(){
        const {isHot} = this.state
        return (
          
{/*内联形式的回调在更新时会执行两次*/} {/*

c}>今天很{isHot?'热':'凉爽'}

*/}

this.changeState} ref={this.changeState}>今天很{isHot?'热':'凉爽'}

this.left = c}/>
) } }

    3、creatRef:React.creatRef()调用后会返回一个储存被ref标识的节点的容器。但该函数只能包含一个节点,如果有多个节点需要使用ref,则要创建多个

    class Person extends React.Component{
      myRef = React.createRef()
      myRef2 = React.createRef()
      showData=()=>{
        alert(this.myRef.current.value)
      }
      render(){
        return (
          
this.myRef}/>
) } }

    4、ref中的事件处理

      1. 通过onXxx属性指定事件处理函数(注意大小写)

        1) React使用的是自定义(合成)事件, 而不是使用的原生DOM事件——兼容性

        2) React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)——性能

      2. 通过event.target得到发生事件的DOM元素对象——不要过渡使用ref,发生事件的节点正好是要操作的节点的时候可以用event.target拿到

    class Person extends React.Component{
      showData=(event)=>{
        alert(event.target.value)
      }
      render(){
        return (
          
onBlur={this.showData}/>
) } }

4、表单数据:表单提交会默认触发页面跳转。Vue的双向绑定,React没有实现,所以可以手动用onChange实现

  非受控组件:现用现取,等值输入完了再去拿

    class Login extends React.Component{
      collect = (e)=>{
        e.preventDefault()
        const {user, psw} = this
        alert(`账号为:${user.value};密码为:${psw.value}`)
      }
      render(){
        return (
          
this.collect}> 用户名:<input ref={c=>this.user=c} type="text"/>
密码:<input ref={c=>this.psw=c} type="password"/>
) } }

  受控组件:随用随取,state随时维护输入的值。少用ref

    class Login extends React.Component{
      state ={name:'',psw:''}
      dataName=(e)=>{
        this.setState({name:e.target.value})
      }
      dataPsw=(e)=>{
        this.setState({psw:e.target.value})
      }
      collect = (e)=>{
        e.preventDefault()
        const {name, psw} = this.state
        alert(`账号为:${name};密码为:${psw}`)
      }
      render(){
        return (
          
this.collect}> 用户名:onChange={this.dataName} type="text"/>
密码:onChange={this.dataPsw} type="password"/>
) } }

5、高阶函数:如果参数或者返回值是函数,那就是高阶函数,比如闭包、Promise、setTimeout、数组的操作等

    class Login extends React.Component{
      state ={name:'',psw:''}
      data=(dataType)=>{
        //因为回调函数加了括号会立即执行,会没有返回值。这里使用闭包,将函数作为返回值
     //e是onChange给的,所以要在返回的函数里传进来 return (e)=>{ //这里的dataType在赋值对象的时候,不能直接引用,对象会默认解析为字符串dataType //逻辑就是a=name,对象的Obj.a和Obj[a]的区别 this.setState({[dataType]:e.target.value}) } } collect = (e)=>{ e.preventDefault() const {name, psw} = this.state alert(`账号为:${name};密码为:${psw}`) } render(){ return (
this.collect}> 用户名:this.data('name')} type="text"/>
密码:this.data('psw')} type="password"/>
) } }

  函数柯里化多态。根据参数,处理不同参数

  纯函数:1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

      2. 必须遵守以下一些约束  

        1) 不得改写参数数据

        2) 不会产生任何副作用,不能做例如网络请求(失败),输入和输出设备(丢失)

        3) 不能调用Date.now()或者Math.random()等不纯,会改变的方法  

      3. redux的reducer函数必须是一个纯函数

6、组件的生命周期

  1、 组件从创建到死亡它会经历一些特定的阶段。React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

  2、旧生命周期

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染:1. constructor();2. componentWillMount();3. render();4. componentDidMount()
  • 更新阶段:由组件内部this.setSate()或父组件重新render触发:1. shouldComponentUpdate();2. componentWillUpdate();3. render();4. componentDidUpdate();

  • 卸载阶段:由ReactDOM.unmountComponentAtNode()触发:componentWillUnmount()

   3、新生命周期

  • 初始化阶段: 由ReactDOM.render()触发---初次渲染:1. constructor();2. getDerivedStateFromProps();3. render();4. componentDidMount()
  • 更新阶段:由组件内部this.setSate()或父组件重新render触发:1. getDerivedStateFromProps(); 2. shouldComponentUpdate();3. render();4. getSnapshotBeforeUpdate(); 5. componentDidUpdate();

  • 卸载阶段:由ReactDOM.unmountComponentAtNode()触发:componentWillUnmount()

  新React考虑使用异步渲染,为了防止滥用误用出现未知bug,所以有三个旧钩子可能被删除:componentWillMount();componentWillUpdate();componentWillReceiveProps();

  添加了两个新钩子:getDerivedStateFromProps(); getSnapshotBeforeUpdate(); 

    1、getDerivedStateFromProps(props,state); 静态方法,参数接收props/state,控制state的渲染,state的状态完全取决于所返回的是props还是state。如果需要state的值一直等于props的值,就去用。一般不会去控制。

      static getDerivedStateFromProps(props,state){
        console.log('getDerivedStateFromProps');
        // return null
        // return props
        return state
      }

    2、getSnapshotBeforeUpdate();在componentDidUpdate的时候会传入三个参数,preProps、preState和snapshot。一般用来捕获在渲染前的一些信息,因为再往下就渲染完成,获取不到之前的一些数值了。比如滚动,一般也不用.

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 我们是否在 list 中添加新的 items ?
    // 捕获滚动??位置以便我们稍后调整滚动位置。
    if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
    // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
    //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
    if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }

  4、主要考虑render、componentDidMount、componentWillUnmount三个钩子

7、react项目目录:

  public ---- 静态资源文件夹

  • favicon.icon ------ 网站页签图标
  • index.html -------- 主页面
  • logo192.png ------- logo图
  • logo512.png ------- logo图
  • manifest.json ----- 应用加壳的配置文件
  • robots.txt -------- 爬虫协议文件

  src ---- 源码文件夹

  • App.css -------- App组件的样式
  • App.js --------- App组件
  • App.test.js ---- 用于给App做测试
  • index.css ------ 样式
  • index.js ------- 入口文件
  • logo.svg ------- logo图
  • reportWebVitals.js--- 页面性能分析文件(需要web-vitals库的支持)
  • setupTests.js---- 组件单元测试的文件(需要jest-dom库的支持)

8、样式要注意,因为其不像Vue有Scope,所以要注意样式的模块化:1、最简单的其实还是class不同名;2、使用模块化引入样式index.module.cs,一般不用;3、less;4、style-loader css-loader等等

9、组件开发流程:

  • 拆分组件: 拆分界面,抽取组件
  • 实现静态组件: 使用组件实现静态页面效果
  • 实现动态组件
    •   动态显示初始化数据
      •  数据类型
      •  数据名称
      •  保存在哪个组件?
    •   交互(从绑定事件监听开始)

10、组件信息传递:

  首先要从架构上考虑状态应该放在哪个组件最合适。操作状态的函数也将会放在该组件内(状态提升)。React是单向数据流

  父传子:props

  子传父:也可以用props,父组件给子组件传递函数,子组件执行父组件的函数,通过传参把新的props传回给父组件操作

  兄弟互传:子传父传子

  任意组件之间:消息订阅与发布机制PubSubJS:一个组件发布消息与参数('topic', data),另一个组件订阅某一消息并响应执行回调获取参数('topic', responFn)

// 订阅者收到消息后的回调函数
var mySubscriber = function (msg, data) {
    console.log( msg, data );
};

// 订阅者,可以把它挂载在某一个钩子上,一般可以放在componentDidMount
var token = PubSub.subscribe('MY TOPIC', mySubscriber);

// 发布者
PubSub.publish('MY TOPIC', 'hello world!');

//取消订阅
PubSub.unsubscribe(token)
//异步发布 PubSub.publishSync('MY TOPIC', 'hello world!');

11、React路由

  1、单页面SPA:整个应用只有一个完整的页面。点击页面中的链接不会刷新页面,只会做页面的局部更新数据都需要通过ajax请求获取, 并在前端异步展现。

  2、路由:一个路由就是一个映射关系(key:value)。其基础就是栈结构

      具体规则:1、hash,Hash 方法是在路由中带有一个#锚点,通过监听 # 后的哈希值 URL 路径标识符的更改而触发的浏览器 hashchange 事件,然后通过获取 location.hash 得到当前的路径标识符,再进行一些路由跳转的操作,其不会页面跳转。localhost -> localhost/#/test1 ->l ocalhost/#/test2,只监听#后的变化。不美观

           2、history API,纯history操作路径通过push、replace将路径入栈出栈,go、goBack和goForward实现路径的浏览。history 提供了pushState(新增)和 replaceState(替换) 两个方法,这两个方法改变 URL 的 path 部分不会引起页面刷新。但是是H5的,兼容性没hash好

    前端路由的核心就是以上两点,其他的功能类似动态路由、路由参数、路由动画,都是添加上的功能。

  3、常用组件:

    1.  history Api,一般来说整个应用是用一个路由器管理,所以该标签一般定义在index.js上。其刷新网页,维护history导致state不会消失

    2.  hash,同上。HashRouter刷新后,因为其底层原理并不是使用history,所以会导致路由state参数的丢失

    3.  path对应路由,component对应路由组件path="/about" component={About} />(Router6中可配合useRoutes()配置路由表

    4.  重定向,类似自动跳转,开屏默认跳转的路由,登录成功后自动跳转之前的页面等。原理就是当其他都没匹配上,就再去找Redirect。(Router6改成

    5.  类似,to到指定路由to="/home">Home

    6.  如果点击,即追加类名activeClassName,动态激活。和Vue的RouterLink类似,依据当前路由路径和to判断是否一致来添加class。(Router6需要className的值改成函数,点击都会调用,根据参数isActive,更改类名。home子组件匹配成功,默认home高亮;若添加end属性,home不高亮

    7. 路由是按定义顺序匹配,为了提高路由匹配效率,只要匹配到,立马结束,不往下匹配。单一匹配,一般一个路由一个组件。(Router6改成,且必须)

  4、一般组件:引用渲染

这类,props默认为空

    路由组件:路由渲染这类,props默认传和路由相关的东西,比如location、history和match等,类似Vue的route。这一些其实基本就是原生的BOM内容了。

      以下展示的是主要的信息,有些未展示:

      location:当前路由信息。hash/pathname/search/state

      history:管理历史路由,控制路由前进后退等。go/goBack/goForward/push/replace

      match:匹配模式,参数。isExact/params/path/url

  5、封装路由组件:其实也是当一个一般组件rcc/rfc编写,因为是用标签引入的。import路由组件的时候,根据需要,看是引入Link还是NavLink。注意标签体的传递,也是在props里,属于children,可以直接写入标签内

//封装组件
import React, { Component } from 'react'
import { NavLink, } from "react-router-dom";

export default class myNavLink extends Component {
  render() {
    return (
      {...this.props}>
    )
  }
}

//引用
Home
About

  6、样式路径问题:如果在路由路径中使用了多级路由,在index.html引入样式时,刷新重定向后,再次请求的css文件路径可能会出现问题。可以处理一下引用路径。比如引入css时,./换成/;或者用%PUBLIC_URL%路径,这个在react用得比较多,且只能在react有效;也可以用锚点从而请求路径不带#号后的内容

  7、路径匹配:精准匹配:路由开启exact。路由和link一一对应。一般不要开启,不然会无法匹配二级路由

//路由
exact={true} path="/about/a" component={About} />  //router6: element={}
//Link
/about/a" show="About"/>

          模糊匹配:默认模式,路由去匹配Link,按照Link的顺序去匹配

//路由
/about" component={About} />
//Link
/about/a" show="About"/>
//错误

  8、嵌套路由(多级路由)Vue的子路由:多级下的子组件的Link,需要包含父级的Link。先走的父级的Link,再走多级的Link。否则会匹配不到导致执行父级上的重定向回About。

同理,Link加了路径,Route也需要。这个不太像Vue一样专门一个route文件去编辑

//Route




//Link
News
Message

  9、路由传参:

    1、params,动态路由。路由用/:id类型匹配,Link用模板字符串传。子组件用props.match.params接收

// 父组件
${msgObj.id}/${msgObj.title}`}>{msgObj.title}
:id/:title" component={Detail}/>

// 子组件
const {id,title} = this.props.match.params
//所拿到的对象为{id:msgObj.id的值, title: msgObj.title的值}

    2、search,search参数通过?判断,路由并不需要匹配。获取到的search是urlencoded编码字符串,需要借助querystring解析,slice去掉?号

//父组件
?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}


//子组件
const {search} = this.props.location
const {id,title} = qs.parse(search.slice(1))

    3、state,地址栏不会显示参数。因为link上要加state,而state不是显示在url上,所以这里的to是写成一个对象的形式。history维护location,即使页面刷新,url不刷新,参数也不会丢失,导致刷新其也不会跳转查到初始页面

//父组件
{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}


//子组件
const {id,title} = this.props.location.state || {}

  10、路由跳转(编程式路由导航):默认push模式,使用props.history.下的各类操作函数,push()、replace()、go()等

//比如这个例子里,各个消息都是同级的,就没必要返回的时候返回上一个消息,可以直接返回到home下,而不是3,2,1
replace to={{pathname:'/home/message/detail',state:{id:msgObj.id,title:msgObj.title}}}>{msgObj.title}

  11、withRouter。一般组件是没有路由组件的一些功能的,比如history,所以按道理是不能直接操控前进后退等。在一般组件内引入withRouter,将一般组件包装成路由组件,内部按照路由组件,调用this.props.history.push()等方法即可。

import {withRouter} from 'react-router-dom'
class Header extends Component{
  ...  
}
// 返回一个新组件
export default withRouter(Header)

12、redux。第三方出品

  redux是一个专门用于做状态管理的JS库,可以用在react、angular、vue下。现在还有很多别的,本质上都是发布订阅模式

  • 没有状态管理工具:直接用 props 或者 context
  • 单项数据流:reduxzustand
  • 双向绑定:mobxvaltio
  • 状态原子化:jotairecoil
  • 有限状态机:xstate

  何时要用到状态管理工具?1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。2. 一个组件需要改变另一个组件的状态(通信)。

  1、原理

   2、三大核心(Vue四大或者说五大,状态管理都类似)

     1、action:

        1、动作对象。同步(Vue的mutation的定义)   

        type:标识属性, 值为字符串, 唯一, 必要属性。操作

        data:数据属性, 值类型任意, 可选属性。数据

        如:{ type: 'ADD_STUDENT',data:{name: 'tom',age:18} }

export const createIncrementAction = data => ({type:INCREMENT,data}) //箭头函数直接返回一个对象需要用括号包着,否则会和函数体的花括号冲突

        2、函数。异步(Vue的action)

//这里常规的会写成store.dispatch调用分发,但是在store拦截action的时候,会查看action,其里面要用一般对象,而不能用函数,所以
          action的值为函数,异步action中一般都会调用同步action,异步action不是必须要用的。
//action内
export const createIncrementAsyncAction = (data,time) => {
    //这里store调用的dispatch,所以也会默认的传一个dispatch过来。当然,在action再引用store,再store.dispacth也可以
return (dispatch)=>{ setTimeout(()=>{        //这里通常会理解写成store.dispatch...,但是在store拦截action的时候其检查内部不是对象(这里我们写成了函数)会报错        //所以我们使用中间件去封装dispatch:redux-thunk,有了这个才能异步 dispatch(createIncrementAction(data)) },time) } }

//store内引入redux-thunk
//引入redux-thunk,用于支持异步action import thunk from 'redux-thunk' //暴露store export default createStore(countReducer,applyMiddleware(thunk))  

    2、reducer(Vue的mutation:初始化状态(undefined,初始化),加工状态(旧state,动作)。其是根据旧的state和action, 产生新的state的纯函数,preState不能改变。

const initState = 0 //初始化状态
export default function countReducer(preState=initState,action){
    //从action对象中获取:type、data
    const {type,data} = action
    //根据type决定如何加工数据
    switch (type) {
        case INCREMENT: //如果是加
     //这里要注意,如果preState是一个对象,比如数组,若直接返回preState则不生效,因为redux会做对比,不同才return新值。
     //所以可一个解构返回新数组
return preState + data case DECREMENT: //若果是减 return preState - data default: return preState } }

    3、store(Vue的state):将state、action、reducer联系在一起的对象 

        创建store:

        1) import {createStore} from 'redux'

        2) import reducer from './reducers'   --> 纯操作函数

        3) const store = createStore(reducer)

        使用store的步骤:

        1) store.getState(): 得到state

        2) store.dispatch(action): 分发action, 触发reducer调用, 产生新的state

        3) store.subscribe(listener): 注册监听, 当产生了新的state时, 自动调用,执行代码,如render更新视图:我们不会去直接this.render()去调用渲染,而是会setState({}),只要调用setState,都会去执行render。改方法可以写在总的index里,只要写一次就行,整体diff

import store from './redux/store'

store.subscribe(()=>{
    ReactDOM.render(,document.getElementById('root'))
})

    tips:可以把action的一些操作名称统一通constant.js去维护,防止写错。这不单只是在redux里,算是编程的一个知识点:

export const INCREMENT = 'increment'
export const DECREMENT = 'decrement'

13、react-redux。官方

   概念:

   UI:

  • 只负责 UI 的呈现,不带有任何业务逻辑
  • 通过props接收数据(一般数据和函数)

  • 不使用任何 Redux 的 API

  • 一般保存在components文件夹下

  容器(一般就是react-redux生成):

  • 负责管理数据和业务逻辑,不负责UI的呈现

  • 使用 Redux 的 API

  • 可以自动检测store变化,并自动render,不需要subscribe再render
  • 一般保存在containers文件夹下,也可以把UI和容器写一起

  1、连接:

//引入connect用于连接UI组件与redux
import {connect} from 'react-redux'
//使用connect()()创建并暴露一个Count的容器组件,必须传两个函数mapStateToProps,mapDispatchToProps export default connect(mapStateToProps,mapDispatchToProps)(CountUI)

  mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性,传状态

  mapDispatchToProps:将分发action的函数转换为UI组件的标签属性,传方法

  简写,把mapDispatch函数简写成对象:

connect(
    state => ({key:value}), //映射状态,数据                                    
    {key:xxxxxAction} //映射操作状态的方法,操作。react-redux能dispatch自动分发,不用再写成函数的形式
)(UI组件)

  2、store的定义:而且因为容器包裹UI,store之和容器打交道,所以store只要在index.js里,在大封装APP,也就是app.jsx的上一层使用即可。使用Provider

import store from './redux/store'
import {Provider} from 'react-redux'

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

  如果要给个别的容器使用,也可以在app.jsx里使用

import React, { Component } from 'react'
import Count from './containers/Count'
import store from './redux/store'

export default class App extends Component {
    render() {
        return (
            
{/* 给容器组件传递store */}
) } }

  3、数据共享:store共享、acitons文件共用、reducers文件公用(combineReducers合并生成对象,否则只会执行一个reducer,只能保存一种数据结构)

//汇总所有的reducer变为一个总的reducer
const allReducer = combineReducers({
    he:countReducer,
    rens:personReducer
})

//暴露store, 现阶段createStore已经弃用,现用legacy_createStore
export default createStore(allReducer,applyMiddleware(thunk))

14、补充

   1、setState更新状态的2种写法

    (1). setState(stateChange, [callback])------对象式的setState
      1.stateChange为状态改变对象(该对象可以体现出状态的更改),就是setState({count: count+1})
      2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用。nextTick

    this.setState({ count: count+1 },()=>{
      console.log(count);
    });

    (2). setState(updater, [callback])------函数式的setState
      1.updater为返回stateChange对象的函数
      2.updater可以接收到state和props
      3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

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

    总结:
    1.对象式的setState是函数式的setState的简写方式(语法糖)
    2.使用原则:
      (1).如果新状态不依赖于原状态 ===> 使用对象方式
      (2).如果新状态依赖于原状态 ===> 使用函数方式,当然,对象式其实也可以。{count: this.state.count+1}

      (3).如果需要在setState()执行后获取最新的状态数据,要在第二个callback函数中读取

  2、路由组件lazyload:

    //1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
    const Login = lazy(()=>import('@/pages/Login'))
    
    //2.通过指定在加载得到路由打包文件前显示一个自定义loading界面
    

loading.....

}>

  3、Hooks:可以在函数组件中使用 state 以及其他的 React 特性。函数式组件没有this

  (1). State Hook: React.useState()

  (2). Effect Hook: React.useEffect()

  (3). Ref Hook: React.useRef()

    1、State Hook

      (1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作

      (2). 语法: const [xxx, setXxx] = React.useState(initValue)
      (3). useState()说明:
        参数: 第一次初始化指定的值在内部作缓存
        返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
      (4). setXxx()2种写法:
        setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
        setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

export default function Index(props) {
  const [count, setCount] = React.useState(0);
  const [person] = React.useState({ name: "tom", age: 18 });
  function add() {
    // setCount(count=>count+1)
    setCount((count) => count + 1);
  }
  return (
    

当前求和:{count}

当前姓名:{person.name}

当前年龄:{person.age}

); }

    2、Effect Hook

      (1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
      (2). React中的副作用、使用生命周期操作:
        发ajax请求数据获取
        设置订阅 / 启动定时器
        手动更改真实DOM
      (3). 语法和说明:
        useEffect(() => {
          // 在此可以执行任何带副作用操作
          return () => { // 在组件卸载前执行
          // 在此做一些收尾工作, 比如清除定时器/取消订阅等
          }
        }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行。如果不传,则监测所有state;如果传空数组,则谁也不监测

  //effect hook 相当于三个生命钩子
  React.useEffect(() => {
    //DidMount
    let timer = setTimeout(() => {
      setCount((count) => count + 1);
    }, 1000);
    //WillUnmount
    return ()=>{
      clearInterval(timer)
    }
    //只检测count,DidUpdate
  }, [count]);

      (4). 可以把 useEffect Hook 看做如下三个函数的组合
        componentDidMount()
        componentDidUpdate()
        componentWillUnmount()

    3、Ref Hook 

      (1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
      (2). 语法: const refContainer = useRef()
      (3). 作用:保存标签对象,功能与React.createRef()一样

    4、新版React新增多个hook

      useRoutes()配置路由表。类似Vue的路由表,嵌套路由也是和vue一个套路;配合指定路由组件在别的组件内的位置

      useParams()获取路由参数。由于函数式组件没有this,拿不到history里的参数,所以要使用hook,返回qs分割好的对象

      useMatch()获取路由match参数,需要传参路径

      useSearchParams()获取路由Search参数,返回数组,第一个是参数,第二个是setSearch  

      useLocation()获取location参数

      useNavigate()使用history,因为函数式没有this,拿不到history,有时候需要在函数内跳转页面,需要用到history,可以replace,传参等  

      useInRouterContext()判断是否在路由上下文环境内,返回判断

      useNavigationType()返回当前页面导航类型:pop刷新页面、push、replace

      useOutlet()用来呈现当前组件中渲染的嵌套路由,也就是当前组件内的子路由,该子路由需要挂载才能返回

      useResolvedPath()解析路径,传入路径,类似qs          

  4、相当于Vue的template

  5、Context。一种组件间通信方式, 常用于【祖孙组件】与【后代组件】间通信。一般应用开发不用,开发插件用。有些类似Vue的 provide 和 inject

    1) 创建Context容器对象:写在所有组建的外侧,类似redux共享 
      const XxxContext = React.createContext()   首字母大写
    2) 父组件渲染子组时,在子外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:

        value={数据}>
            子组件
        

    3) 后代组件读取数据:

      //第一种方式:仅适用于类组件
      static contextType = xxxContext // 先声明接收context
        this.context // 然后,读取context中的value数据
      //第二种方式: 函数组件与类组件都可以
      .Consumer>
        {value => ( // 函数接收参数value,就是context中的value数据要显示的内容)}
      

  6、PureComponent

    其实主要是优化shouldComponentUpdate。当父组件更新时,控制子组件的更新,比如子组件没用到父组件的数据时,子组件不应该更新。    

      办法1:重写shouldComponentUpdate()方法比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false

     shouldComponentUpdate(nextProps,nextState){
        // console.log(this.props,this.state); //目前的props和state
        // console.log(nextProps,nextState); //接下要变化的目标props,目标state
        return !this.props.carName === nextProps.carName //一般来说子组件的判断用props,因为是获取的props;父组件判断是state
    } 

      办法2:使用PureComponent,PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true

class Child extends PureComponent{}

    注意:
      只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
      不要直接修改state数据, 而是要产生新数据
      项目中一般使用PureComponent来优化

  7、render props(Vue的插槽)向组件内动态传入带内容的标签。就是第三方组件插入插槽

//传统嵌套,children props

  xxxx

{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 
//render props,通过render函数传递
//slot
class A extends Component {
    state = {name:'tom'}
    render() {
        console.log(this.props);
        const {name} = this.state
        return (
            

我是A组件

{this.props.render(name)} //slot插槽预留的位置
) } } //引用 }/> //要插入的B class B extends Component { render() {return (

我是B组件,{this.props.name}

) } }

  8、错误边界:用来捕获后代组件错误,渲染出备用页面,防止整个页面崩溃。类似自定义局域的404页面

    只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

    使用:getDerivedStateFromError配合componentDidCatch。不过一般用在生产环境 ,因为显示的时间较短。

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {
    console.log(error);
    //render之前触发
    // 返回一个新的state状态,将之前定义的state修改。state修改就可以渲染自定义的404或别的错误页面
    return {
        hasError: true,
    };
}

componentDidCatch(error, info) {
    // 统计页面的错误。发送请求发送到后台
    console.log(error, info);
}

  9、组件间通信

  组件间的关系:1、父子组件;2、兄弟组件(非嵌套组件)3、祖孙组件(跨级组件)

  几种通信方式:

    1.props:
      (1).children props
      (2).render props
    2.消息订阅-发布:
      pubs-sub、event等等
    3.集中式管理:
      redux、dva等等
    4.conText:
      生产者-消费者模式,provider

  常见搭配:

    父子组件:props
    兄弟组件:消息订阅-发布、集中式管理
    祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)

官网和其他文档的一些补充:

1、React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

 2、Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。JSX是其语法糖

// 注意:这是简化过的结构
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

  这个称为react元素。虚拟dom

3、State 的更新可能是异步的

  因为 this.props 和 this.state 可能会异步更新,所以不要依赖他们的值来更新下一个状态。要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

4、Ref 转发是一项将ref自动地通过组件传递到其一子组件的技巧。一般也不会用

//子组件
const FancyButton = React.forwardRef((props, ref) => ( )); // 你可以直接获取 DOM button 的 ref:指定ref为jsx的props const ref = React.createRef(); Click me!;

  第二个参数 ref 只在使用 React.forwardRef 定义组件时存在。常规函数和 class 组件不接收 ref 参数,且 props 中也不存在 ref。

5、Fragments。React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。其实类似vue的template生成代码片段,渲染的时候标签会自动消失

//父组件
class Table extends React.Component {
  render() {
    return (
      
        
); } } //子组件 class Columns extends React.Component { render() { return ( Hello World ); } } //唯一根,包裹div会不生效 class Columns extends React.Component { render() { return (
Hello World
); } }

6、高阶组件。高阶组件是参数为组件,返回值为新组件的函数。比如withRouter封装的组件

const NavbarWithRouter = withRouter(Navbar);
// React Redux 的 `connect` 函数
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

  不要在render中使用HOC,不单只是性能问题,还有可能造成数据丢失

render() {
  // 每次调用 render 函数都会创建一个新的 EnhancedComponent
  // EnhancedComponent1 !== EnhancedComponent2
  const EnhancedComponent = enhance(MyComponent);
  // 这将导致子树每次渲染都会进行卸载,和重新挂载的操作!
  return ;
}

7、可以使用点语法来引用一个 React 组件。当你在一个模块中导出许多 React 组件时,例如,如果 MyComponents.DatePicker 是一个组件,可以在 JSX 中直接使用:

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return 
Imagine a {props.color} datepicker here.
; } } function BlueDatePicker() { return ; }

8、由于react的diff并不是很精细,父组件一旦更新,其下子组件也将更新。为了减少性能损失的影响,可以使用shouldComponentUpdate去控制子组件的更新。