前言

1、react与vue的对比

1.1、什么是模块化

是从代码的角度来进行分析的

把一些可复用的代码抽离为单独的模块;便于项目的维护和开发

1.2、什么是组件化

是从UI界面角度来进行分析的

把一些可复用的UI元素抽离为单独的组件

1.3、组件化的好处

随着项目规模的增大,手里的组件就越来越多;

很方便就能把现有的组件拼接为一个完整的页面

1.4、vue中是如何实现组件化的

通过.vue文件来创建对应的组件

template 结构 script 行为 style 样式

1.5、React如何实现组件化:

React中有组件化的概念,但是并没有像.vue这样的组件模板文件

React中,一切都是以JS来表现的

2、虚拟DOM

2.1、DOM的本质是什么

浏览器中的概念,用js对象来表示页面上的元素,并提供了操作dom对象的api

2.2、什么是React中的虚拟DOM

框架中的概念:用js对象来模拟页面上的DOM和DOM嵌套

2.3、为什么要实现虚拟DOM(虚拟DOM的目的)

为了实现页面中,DOM元素的搞笑更新

一、react简介

1、什么是react

  • react 是一个将数据渲染为HTML视图的开源JavaScript库

2、为什么要学react

  • 原生JavaScript操作DOM繁琐效率低(DOM-API操作UI
  • 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
  • 原生JavaScript没有组件化编码方案,代码复用率低

3、react的特点

  • 采用组件化模式、声名式编码,提高开发效率及组件复用率
    • 命令式编码:命令机器如何去做事情,说一步做一步
    • 声名式编码:告诉机器想要什么,机器去想如何去做
  • 在 React Native 中可以使用 React 语法进行移动端开发
  • 使用虚拟 DOM+优秀的 Diffing 算法,尽量减少与真实 DOM 的交互

二、react初体验

1、 所需依赖

// crossorigin 没有这个属性的话浏览器会隐藏跨域页面的报错问题
// 加上这个属性可以使浏览器获取到引入脚本中的具体报错

// babel.js: ES6转ES5,jsx转js
// react.development.js:react核心库  引入之后全局多了React对象
// react-dom.development.js:react扩展库(帮助操作dom)引入之后全局多了ReactDOM对象

<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

2、使用 jsx

  1. 全称:JavaScript XML
  2. react 定义的一种类似于 XML 的 js 扩展语法:js+XML
  3. jsx 是原始 js 创建虚拟 DOM(React.creatElement())的语法糖
  4. 作用:用来简化创建虚拟 DOM
    1. 不是字符串,也不是 HTML/XML 标签
    2. 最终生成的就是一个 js 对象
  5. jsx 语法规则
    1. 定义虚拟 DOM 时,不要写引号
    2. 标签中混入 JS 表达式时要使用 {}
      1. JS 表达式:一个表达式产生一个值(即有返回值),可放在任何一个需要值的地方
        //  1、变量
        a
        //  2、运算表达式
        a+b
        //  3、函数调用表达式 函数默认返回undefined
        demo(1)	
        //  4、数组的map方法 有返回值
        arr.map()
        //  5、定义一个函数 返回函数本身
        function test () {}
        
      2. JS语句:js代码块 都没有返回值,只是控制代码走向
        if(){}
        
        for(){}
        
        switch(){case:xxx}
    3. 样式的类名指定不要用 class,要用 className(为了与es6类区分)
    4. 内联样式,要用 style={{key:value}} 的形式来写
    5. 虚拟 DOM 只有一个根标签
    6. 标签必须闭合
    7. 标签首字母
      1. 若大写字母开头,react 就会去渲染对应的组件,若组件没定义,则报错
      2. 若小写字母开头,则将该标签转为 html 中同名标签,若没有对应标签则报错
    8. <script type="text/babel"></script>
    9. 可以使创建虚拟 dom 节点更加的方便

3、虚拟DOM

  • 本质是 Object 类型的对象(一般对象)
  • 真实 DOM 比虚拟 DOM 重,虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多属性
  • 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。

4、模块与组件、模块化与组件化的理解

4.1、js模块(只是对js进行拆分)

  • 理解:
    • 向外提供特定功能的js程序,一般就是一个js文件
  • 为什么要拆成模块:
    • 随着业务逻辑增加,代码越来越多且复杂
  • 作用:
    • 复用js,简化js的编写,提高js运行效率

4.2、组件()

  • 理解:
    • 用来实现局部功能效果的代码和资源的集合( html/css/js/image 等等 )
  • 为什么:
    • 一个界面的功能更复杂
  • 作用:
    • 复用编码,简化项目编码,提高运行效率

4.3、模块化

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

4.4、组件化

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

三、React面向组件编程

1、组件详解

1.1、函数式组件

  • 函数定义的组件(适用于简单组件的定义)
     <script type="text/babel">
          function Demo() {
          // babel翻译之后打开严格模式,this不能指向window,变为undefined
            console.log("this", this);
            return <h1>函数组件</h1>;
          }
          ReactDOM.render(<Demo />, document.getElementById("app"));
          
           // 执行了ReactDOM.render之后发生了什么?
          // 1、React解析组件标签,找到了Demo组件
          // 2、发现组件是使用函数定义的,随后调用该函数
          // 3、将返回的虚拟DOM转为真实DOM,随后呈现在页面中
         
        </script>

1.2、类组件

  • 使用es6 class 类定义的组件(适用于复杂组件)
        <script type="text/babel">
          // 使用类组件一定要继承React.Component
          class MyComponent extends React.Component {
            // 构造器不是必须的
            // 但一定要有render并且有返回值
            render() {
                // 虽然我们没有手动new MyComponent()进行实例化,但是ReactDOM.render帮我们做了实例化操作
                // 所以 此处 render存在于MyComponent的原型对象上,供实例使用 
                // 此处 render中的this指向MyComponent的实例对象
              console.log("render中的this", this); // 指向MyComponent的实例对象 MyComponent{...}
              return <h1>我是用类定义的组件(使用与【复杂组件】的定义)</h1>;
            }
          }
          // 渲染组件到页面;
          ReactDOM.render(<MyComponent />, document.getElementById("app"));
          
          //执行了ReactDOM.render(<MyComponent />, ...))之后,发生了什么?
          // 1、React解析组件标签,找到了MyComponent组件
          // 2、发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法
          // 3、将render返回的虚拟DOM转为真实DOM,随后呈现在页面中
     
        </script>

2、组件实例的三大特性(类组件/ hooks)

2.1、state

2.1.1、理解

  • state 是组件对象最重要的属性,值是对象
  • 组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)

2.1.2、强烈注意

  • 组件中 render 方法中的this为组件实例对象
  • 组件自定义的方法中 this 为 undefined,如何解决?
    • 强制绑定 this ;通过函数对象的 bind()
    • 箭头函数
  • 状态数据,不能直接修改或更新
<script type="text/babel">
      // 创建组件
      class MyComponent extends React.Component {
        //   构造器调用几次? 1次
        constructor(props) {
          super(props);

          //   初始化状态
          this.state = {
            isHot: false,
          };

          //   this指向的解决办法
          // 左边的this.changeWeather是当前类的changeWeather变量
          // 右边的this.changeWeather是类原型对象上的方法,将其this指向到当前类
          this.changeWeather = this.changeWeather.bind(this);
        }

        // render调用几次?  1+n次,1是初始化,n是状态更新的次数
        render() {
          // render存在于MyComponent类的原型对象上
          // render由MyComponent的实例调用,this指向MyComponent的实例对象
          // 读取状态
          const { isHot } = this.state;
          console.log("render中的this", this); // 指向MyComponent的实例对象 MyComponent{...}
          // onClick 不是html dom事件onclick   不能与原生事件相比较
          // 此处 实际上是将实例上的changeWeather方法赋值给onClick
          // 然后通过onClick去直接调用changeWeather事件,此时this指向就不是实例了
          return (
            <h1 onClick={this.changeWeather}>
              今天的天气{isHot ? "炎热" : "凉爽"}
            </h1>
          );
        }

        // changeWeather调用几次? 点几次调几次
        changeWeather() {
          // changeWeather 是在实例的原型对象上,供实例使用
          // 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
          // 类中的方法默认开启局部的严格模式,所以chagneWeather中的this为undefined
          console.log("changeWeather", this);

          //   严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换
          //   setState是合并的操作不会影响state中其他属性,不是替换
          this.setState({ isHot: !this.state.isHot });
        }
      }
      // 渲染组件到页面;
      ReactDOM.render(<MyComponent />, document.getElementById("app"));

      //   ReactDOM.render都做了写什么;
      //   1、先判断传入的组件标签
      //     是小写就对应html中的标签进行渲染
      //     是大写就判断是否是函数组件或类组件
      //         是函数组件就找到对应的函数组件进行渲染
      //         是类组件就先实例化,然后使实例化调用原型上的render方法渲染
      //     将返回的虚拟dom树转换为真实dom数
    </script>

2、props

2.1、类组件接收props值

<script type="text/babel">
      class Person extends React.Component {
        // 构造器作用只有两个
        // 1、初始化数据状态(可以简写)
        // 2、给事件绑定this对象(可以用赋值语句+箭头函数来简写)
        // 结论:能省略就省略

        constructor(props) {
          // 构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
          super(props);
          console.log("props", props);
          // super中传递props之后可以用this.props访问props
          console.log("this.props", this.props);
        }

        render() {
          const { name, age, sex } = this.props;
          return (
            <ul>
              <li>{name}</li>
              <li>{sex}</li>
              <li>{age + 1}</li>
            </ul>
          );
        }
      }

      let data = { name: "cz", age: "18", sex: "男" };
      // js中属性对象是不能直接展开的,但是在react中有babel和react核心库对其进行了处理,所以这里可以直接用展开符将对象展开
      ReactDOM.render(<Person {...data} />, document.getElementById("app"));
      ReactDOM.render(
        <Person name="xp" age="16" sex="女" />,
        document.getElementById("app")
      );
    </script>

2.2、函数组件接收props值

<script type="text/babel">
      function Person(props) {
        const { name, sex, age } = props;
        return (
          <ul>
            <li>{name}</li>
            <li>{sex}</li>
            <li>{age + 1}</li>
          </ul>
        );
      }

      let data = { name: "cz", age: 18, sex: "男" };
      // js中对象是不能直接展开的,但是在react中有babel和react核心库对其进行了处理,所以这里可以直接用展开符将对象展开
      ReactDOM.render(<Person {...data} />, document.getElementById("app1"));
      ReactDOM.render(
        <Person name="xp" age={16} sex="女" />,
        document.getElementById("app2")
      );
    </script>

2.3、refs (不要过度使用 ref )

  1. 理解
    1. 组件内的标签可以通过 ref 来标识自己(可替代id)
  2. 写法
    1. 字符串写法
      //字符串写法   不推荐(过时,未来会移除)
      //字符串写法会产生一些问题(效率不高)
      <input ref="input1" type="text" placeholder="请输入" />
      // 获取
      this.refs.input1

    2. 回调函数写法
      • 第一种内联方式
            <script type="text/babel">
              class Demo extends React.Component {
                showData = () => {
                  console.log(this);
                  // 如果写的refs的回调写法,则node节点直接赋值给了实例的input1变量上
                  // 就不能再通过refs上取了 const { input1 } = this.refs;
                  // 直接在实例上取
                  const { input1 } = this;
                  alert(input1.value);
                };
                showData2 = () => {
                  const { input2 } = this;
                  alert(input2.value);
                };
                render() {
                  return (
                    <div>
                      {/*
                        1、执行render函数之后会执行render函数中的jsx语句
                        2、遇到jsx中ref为回调写法则会触发里边的回调
                         (特定标签的回调才会被react处理,随便写个标签的表达式会被处理,但回调不会,而且还会报错 例如:xxx={()=>{...}})错误
                        3、在此处会触发一个箭头函数
                        4、运行箭头函数则将箭头函数中的参数(这个参数就是当前节点由react处理)赋值给this.input1
                        5、因为是箭头函数本身没有this,会向上找到render的this 即当前实例
                        6、所以此处是将当前node节点赋值给实例的input1变量上
                      */}
                      <input
                        ref={(currentNode) => {
                          this.input1 = currentNode;
                        }}
                        type="text"
                        placeholder="点击按钮提示数据"
                      />
                      <button style={{ margin: "0 10px" }} onClick={this.showData}>
                        点击提示左侧输入框内容
                      </button>
                      {/* 此处为refs回调写法的简写方式 */}
                      <input
                        onBlur={this.showData2}
                        ref={(c) => (this.input2 = c)}
                        type="text"
                        placeholder="失去焦点提示数据"
                      />
                    </div>
                  );
                }
              }
              ReactDOM.render(<Demo />, document.getElementById("app1"));
            </script>

      • 第二种类绑定方法
        <script type="text/babel">
              class Demo extends React.Component {
                state = { isHot: true };
        
                showInfo = () => {
                  const { input1 } = this;
                  alert(input1.value);
                };
        
                changeWeather = () => {
                  const { isHot } = this.state;
                  this.setState({
                    isHot: !isHot,
                  });
                };
        
                saveInput = (currentNode) => {
                  console.log("currentNode", currentNode);
                };
                render() {
                  const { isHot } = this.state;
                  return (
                    <div>
                      <h2>今天天气很{isHot ? "炎热" : "凉爽"}</h2>
                      {/*
                        1、此处为内联写法(直接将执行的代码放在回调函数里的方式)
                        2、这样的写法会导致 每次更新(除去第一次render)render时,会重新加载当前函数
                          会执行两次,第一次会把参数赋值为null,第二次才会将当前dom节点绑定上
                        3、对实际代码不会产生影响,依旧可以使用
                      */}
                      <input
                        ref={(currentNode) => {
                          this.input1 = currentNode;
                          console.log("我被调用了", currentNode);
                        }}
                        type="text"
                      />
                      <button onClick={this.showInfo}>点击提示输入的数据</button>
                      <button onClick={this.changeWeather}>点击我改变天气</button>
                      {/*
                        此处为类绑定回调
                        此方法可以避免ref加载两次的问题
                      */}
                      <input ref={this.saveInput} type="text" />
                    </div>
                  );
                }
              }
              ReactDOM.render(<Demo />, document.getElementById("app1"));
            </script>

      • 第三种 createRef (最推荐的方式)
        <script type="text/babel">
              class Demo extends React.Component {
                // React.createRef调用后可以返回一个容器
                // 该容器可以存储被ref所表示的节点
                // 该容器为专人专用,只能存一个,后边的会覆盖当前的
                myRef = React.createRef();
                showData = () => {
                  console.log("myRef", this.myRef); // {current: input}
                };
                render() {
                  return (
                    <div>
                      <input
                        ref={this.myRef}
                        type="text"
                        placeholder="点击按钮提示数据"
                      />
                      <button style={{ margin: "0 10px" }} onClick={this.showData}>
                        点击提示左侧输入框内容
                      </button>
                    </div>
                  );
                }
              }
              ReactDOM.render(<Demo />, document.getElementById("app1"));
            </script>

    3. 事件处理
      • 通过 onXxx 属性指定事件处理函数(注意大小写)
        • React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件 -----为了更好的兼容性
        • React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)----为了高效
      • 通过 event.target 得到发生时间的 DOM 元素对象----不要过度使用ref
      • 当发生事件的元素刚好是我们需要获取的元素就可以不用写 ref 了

3、收集表单数据

  • 非受控组件
  • 受控组件
    • 表单里所有输入类的 dom 在输入的同时能够拿到输入的值

4、高阶函数、柯里化

4.1、高阶函数

  • 如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
    • 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
    • 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
  • 常见的高阶函数
    • promise
    • setTimeout
    • 数组常见方法 map reduce forEach

4.2、函数柯里化

  • 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式

5、生命周期

5.1、理解

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

5.2、生命周期流程(旧)

  • 初始化阶段:由 ReactDOM.render() 触发-----初次渲染
    • constructor
    • componentWillMount()
    • render()
    • componentDidMount() -----常用
      • 一般在这个钩子中做初始化的事
        • 开启定时器
        • 发送网络请求
        • 订阅消息
  • 更新阶段:由组件内部 this.setState() 父组件 render 触发
    • shouldComponentUpdate()
    • componentWillUpdate()
    • render()
    • componentDidUpdate()
  • 卸载组件:由 ReactDOM.unmountComponentAtNode() 触发
    • componentWillUnmount() -----常用
      • 一般在这个钩子中做一些收尾的事
        • 关闭定时器
        • 取消订阅消息
  • 组件接收新的 props 钩子 (第一次接收的不算
    • componentWillReceiveProps()

5.3、生命周期(新)

  • 所有带 will 的钩子除了即将卸载的钩子都要加 UNSAFE_  前缀
  • 这些还是之前的钩子,以后即将废弃,增加它们现在的使用成本
    • componentWillMount() ----> UNSAFE_componentWillMount()
    • componentWillUpdate() ----> UNSAFE_componentWillUpdate()
    • componentWillReceiveProps() ----> UNSAFE_componentWillReceiveProps()
  • 增加了2个新的钩子
    • getDerivedStataeFromProps
    • getSnapshotBeforeUpdate

  • 初始化阶段:由 ReactDOM.render() 触发 ---- 初次渲染
    • constructor()
    • getDerivedStateromProps()
    • render()
    • componentDidMount() ===> 常用
      • 一般在这个钩子中做一些初始化的事情
        • 开启定时器
        • 发送网络请求
        • 订阅消息
  • 更新阶段:由组件内部 this.setState() 或父组件重新render触发
    • getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  • 卸载组件:由 React.unmountComponentAtNode() 触发
    • componentWillUnmount() ====》 常用
      • 一般在这个钩子中做一些首尾的事情
        • 关闭定时器
        • 取消订阅消息

6、diff算法

7、React 发送网络请求

  • 跨域

三、React路由

1、SPA的理解

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

2、路由的理解

  • 什么是路由?
    • 一个路由就是一个映射关系( key:value )
    • key 为路径, value 可能是 function 或 component
  • 路由分类
    • 后端路由
      • 理解: value 是 function , 用来处理客户端提交的请求。、
      • 注册路由: router.get(path, function(req, res))
      • 工作过程:当 node 接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
    • 前端路由
      • 浏览器端路由,value 是 component,用于展示页面内容。
      • 注册路由:
      • 工作过程:当浏览器的 path 变为 /test 时, 当前路由组件就会变为 Test 组件

3、路由的基本使用

  • 明确好界面中的导航区,展示区
  • 导航区的a标签改为Link标签
// to后边写的为组件对应的域名后想要显示的名字
<Link to='/Demo'>Demo</Link>
  • 展示区写Route标签进行路径的匹配
// 要先引入Demo组件
import Demo from './components/demo'
// 只要跟link中的to后的path对应上就渲染
<Route path='/Demo' component={Demo}>
  • 的最外侧包裹了一个或标签
    • 所有的路由只能被同一个路由标签包裹才能起到匹配渲染的作用

4、路由组件与一般组件

  • 写法不同
    • 一般组件
      • 可以通过 export default withRouter(组件名) 方式修改为路由组件
    • 路由组件:
  • 存放位置不同
    • 一般组件: components
    • 路由组件: pages
  • 接受props不同
    • 一般组件:写组件时传递什么接收什么
    • 路由组件:接收到路由信息 有三个固定属性
      • history
      • location
      • match

5、NavLink 与 封装NavLink

  • NavLink可以实现路由链接的高亮,通过activeClassName属性来指定高亮样式名
  • 标签体内容是一个特殊的标签属性
  • 通过this.props.children可以获取标签体内容

6、switch的使用

  • 通常情况写,path 和 component 是一一对应的关系
  • Switch标签包裹Route 标签可以提高路由匹配效率(匹配上一个就不往下进行了)

7、解决多级路径刷新页面样式丢失的问题

  • publich/index.html 中 引入样式时 路径 ./ 修改为 / (常用)
  • publich/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)(react脚手架里)
  • 使用HashRouter

8、路由的严格匹配与模糊匹配

  • 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】且顺序要一致)
  • 开启严格匹配:
  • 严格模式不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

9、Redirect的使用

  • 一般所有的路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
  • 具体编码:
<Switch>
    <Route path="/cz/home" component={Home}></Route>
    <Route path="/about" component={About}></Route>
    <Redirect to="/cz/home" />
  </Switch>

10、嵌套路由

  • 注册子路由是要写上副路由的path值
  • 路由的匹配是按照注册路由的顺序进行的

11、向路由组件传递参数

  • params参数
// 路由链接(携带参数)
<Link to='/detail/03/cz'>详情</Link>
// 注册路由(声明接收)
<Route path="/detail/:id/:name"></Route>
// 接收参数
const {id,name}= this.props.match.params

12、search参数

// 路由链接(携带参数)
 <Link to="/home/news?id=03&name=cz">news</Link>
 // 注册路由(无需声明接收)
 <Route path="/home/news" component={News}></Route>
 // 接收参数(获取到的为urlencoded编码字符串,可通过qs.parse解析)
 const {search} = this.props.location
 const searchQuery = qs.parse(search.slice(1))

13、state参数

// 路由链接(携带参数)
 <Link to={{pathname:'/home/detail',state:{id:'03',name:'cz'}}}>news</Link>
 // 注册路由(无需声明接收)
 <Route path="/home/detail" component={Detail}></Route>
 // 接收参数(获取到的为urlencoded编码字符串,可通过qs.parse解析)
 const {state} = this.props.location

14、编程式路由导航

  • 借助this.props.history对象上的API对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()

15、BrowserRouter 与 HashRouter 的区别

  • 底层原理不一样:
    • BrowserRouter使用的是H5的 history API,不兼容IE9以下的版本
    • HashRouter使用的是URL的哈希值
  • url 表现形式不一样
    • BrowserRouter 的路径中没有#
    • HashRouter 使用的是URL的哈希值
  • 刷新后对路由state参数的影响
    • BrowserRouter 没有任何影响,因为state保存在history对象中
    • HashRouter 刷新后会导致路由 state 参数的丢失!!!
  • 备注:HashRouter 可以用于解决一些路径错误相关的问题

四、Redux

1、Redux是什么

  • redux是一个专门用于做状态管理的JS库(不是react插件库)。
  • 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  • 作用: 集中式管理react应用中多个组件共享的状态。

2、什么情况下需要使用redux

  • 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  • 一个组件需要改变另一个组件的状态(通信)。
  • 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

3、redux工作流程

 4、redux的三个概念

4.1、action

  1. 动作的对象
  2. 包含2个属性
    • type:标识属性,值为字符串,唯一,必要属性
    • data:数据属性,值类型任意,可选属性
  3. 例子
    {
        type:'add',
        data:{name:'cz',age:18}
    }

4.2、reducer

  1. 用于初始化状态,加工状态
  2. 加工时,根据旧的 state 和 action ,产生新的 state 的纯函数
  3. 纯函数:
    • 只要有同样的输入,必定得到同样的输出
    • 必须遵守以下约束
      • 不能改写参数数据
      • 不会产生任何副作用,例如网络请求,输入和输出设备
      • 不能调用 Date.now() 或者 Math.random() 等不纯的方法
  4. 为什么 reducer 要是一个纯函数
    • 首先 reducer 被设计出来是为了接受一个旧 state 和 action 返回一个新的 state 的过程
    • 如果使用不纯的函数,那么返回的 state 将无法被保障
    • 比如 value=getValue+1;getValue 是一个请求,如果清楚出现错误,那么返回的state 将变得无法确定,前端拿到的数据也不确定,就失去了作为 reducer 的价值

4.3、store

  1. 将 state、action、reducer 联系在一起的对象

5、 redux使用

5.1、去除组件自身的状态

5.2、创建store.js

  • 引入redux中的createStore函数,创建一个store
  • createStore调用时要传入一个为起服务的reducer
  • 暴露store对象
  • 创建reducer.js
    • reducer的本质是一个函数,接受preState,action两个参数,返回加工后的状态
    • reducer有两个作用:
      • 初始化状态
      • 加工状态
    • reducer第一次被调用时,是store自动触发的,传递的preState是undefined
  • 在入口文件js中检测store中状态的改变,一旦发生改变重新渲染
  • 备注:redux只负责管理状态,至于状态的改变驱动页面的展示,要靠我们自己写
import store from '../redux/store'
// 监听store状态变化
store.subscribe(()=>{ReactDOM.render(<App />,document.getElementById('root'))})

6、redux 异步使用

  • 明确延迟的动作不想交给组件自身,想交给action去处理
  • 何时需要异步action
    • 想要对状态进行操作,但是具体的数据靠异步任务返回
    • 创建action的函数不再返回一个一般对象,而是返回一个函数,在该函数中写异步任务
    • 异步任务有结果后,分发一个同步的action去真正操作数据
  • 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发action。
  • 具体步骤
    • 需要在store页面中引入applyMiddleware方法去解析中间件
    • 下载并引入react-thunk中间件去解析action返回的函数
store.js
// 该问见专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore,用来创建redux中最为核心的store对象
// 引入 applyMiddleware ,用来处理中间件
import { createStore,applyMiddleware } from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer.js'
// 引入可以处理action为函数的异步方法的中间件redux-thunk
import thunk from 'redux-thunk'
export default createStore(countReducer,applyMiddleware(thunk))

action.js
// action为一个方法时,为异步, store不认识为函数的action,需要用中间件去处理
export const actionAsyncAdd = (data,time) => {
    // store会在调用该方法时将dispath传入
    return (dispatch)=>{
        setTimeout(()=>{
            dispatch(actionAdd(data))
        },time)
    }
}

7、react-redux 使用

7.1、原理图

7.2、react-redux使用

  1. 创建store 同 Redux 方式一样,全局有且只有一个store
  2. 在容器外部用包裹并传入store
    • 传入的store可以在connect中传入的第一个函数内获取到
  3. 引入react-redux中的connect方法包装子组件形成父子关系,并暴露
    export default connect(
        state => ({ count: state.count }), // 接收store中的数据
        {
          jia: ()=>{},  // 传入操作数据状态的方法
          jian: ()=>{},
        })(Count);  // 将子组件传入

  4. 子组件中通过 props 来获取和操作 connect 传递的数据和方法
  5. react-redux 优化
    • 容器组件和UI组件整合成一个文件
    • 无需自己给容器组件传递 store,给包裹一个即可
    • 使用了 react-redux 后不用自己检测 redux 中状态的改变,容器组件可以自动完成这个工作
    • mapDispatchToProps 也可以简写成一个对象
    • 一个组件要和redux“打交道”需要经过哪几步?
      • 定义好UI组件
      • 引入 connect 生成一个容器组件并暴露,写法如下
        connect(
            state=>({key:value}) //映射状态
            {key:xxxxAction} //映射的操作状态的方法
        )(UI组件)

      • 在UI组件中通过this.props.xxxx 读取和操作状态

8、reducer 合并 数据共享

// 该问见专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore,用来创建redux中最为核心的store对象
// 引入 applyMiddleware ,用来处理中间件
// 引入 combineReducers,用来合并reducer
import { createStore,applyMiddleware,combineReducers } from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer.js'
// 引入为person组件服务的reducer
import personReducer from './person_reducer.js'

const allReducers = combineReducers({
    count:countReducer,
    personReducer:personReducer
})
 

// 引入可以处理action为函数的异步方法的中间件redux-thunk
import thunk from 'redux-thunk'
// 将合并后的reducers交给store
export default createStore(allReducers,applyMiddleware(thunk))
  • 定义一Person组件,和 Count 组件通过 redux 共享数据
  • 为 Person 组件编写 reducer,action
  • 重点:Person 的 reducer 和 Count 的 reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象!!!
  • 交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得“取到位”

五、react扩展

1、setState

1.1、第一种写法:setState( {key:value} , [callback] ) ---对象式写法

  • setState是个同步方法,但是setState调用后引起的react状态更新是异步的
  • setState第二个可选的回调参数
    • 它在状态更新完毕、界面也更新后(render调用后)才被调用

1.2、第二种写法: setState( updater, [callback] ) -----函数式写法

  • updater为一个返回一个更改对象的函数
  • updater可以接受到 state 和 props 两个参数
  • callback是可选的回调函数,它在状态更新,界面也更新后(render调用后)才被调用
  • 什么时候表现“同步”什么时候表现“异步”
    • setState内部有合并修改的策略,为的就是优化性能,这一策略导致setState之后渲染会出现异步的情况
    • 所以当我们使用react可以控制的api的时候,都将体现出“异步”的情况
      • react 生命周期,react事件,等等
    • 当我们使用react无法控制的api就可以脱离setState合并修改的操作,使setState变为同步操作
      • setTimeout , promise.then,ajax,绑定原生事件

2、lazyLoad

2.1、路由组件的lazyLoad

//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
	const Login = lazy(()=>import('@/pages/Login'))
	
	//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
	<Suspense fallback={<h1>loading.....</h1>}>
        <Switch>
            <Route path="/xxx" component={Xxxx}/>
            <Redirect to="/login"/>
        </Switch>
    </Suspense>

3、Hooks

3.1、React Hook/Hooks是什么?

  1. Hook是React 16.8.0版本增加的新特性/新语法
  2. 可以让你在函数组件中使用 state 以及其他的 React 特性

3.2、 三个常用的Hook

  1. State Hook: React.useState()
  2. Effect Hook: React.useEffect()
  3. Ref Hook: React.useRef()

3.3、 State Hook

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

3.4、Effect Hook

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

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

3.5、Ref Hook

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

4、Fragment

使用: 

<Fragment><Fragment>
	<></>

作用: 可以不用必须有一个真实的DOM根标签了

5、Context

理解:

> 一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

  1. 创建Context容器对象:
    1. const XxxContext = React.createContext()
  2. 渲染子组时,外面包裹 xxxContext.Provider , 通过 value 属性给后代组件传递数据:
    <xxxContext.Provider value={数据}>
    		子组件
        </xxxContext.Provider>
  3.  后代组件读取数据:
    1. 第一种方式:仅适用于类组件
      1. static contextType = xxxContext // 声明接收context
      2. this.context // 读取context中的value数据
    2. 第二种方式: 函数组件与类组件都可以
      <xxxContext.Consumer>
      	    {
      	      value => ( // value就是context中的value数据
      	        要显示的内容
      	      )
      	    }
      </xxxContext.Consumer>

注意

在应用开发中一般不用context, 一般都用它的封装react插件

6、组件优化

6.1、Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

6.2、效率高的做法

        只有当组件的state或props数据发生改变时才重新render()

原因:

        Component中的shouldComponentUpdate()总是返回true

解决:

        办法1:

                重写 shouldComponentUpdate() 方法

                比较新旧 state 或 props 数据, 如果有变化才返回 true , 如果没有返回 false

        办法2:

                使用 PureComponent

                PureComponent 重写了 shouldComponentUpdate() , 只有 state 或 props 数据有变化才返回 true

        注意:

                只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false

                不要直接修改state数据, 而是要产生新数据

                项目中一般使用PureComponent来优化

7、组件通信方式总结

7.1、组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

7.2、几种通信方式:

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

7.3、 比较好的搭配方式:

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

前往低代码交流专区

更多推荐