网站公告

最近任务:三方业务接入、低代码表单集成

主站地址: http://117.72.70.166/admin/

QQ群:972107977

Skip to content

React

  1. 特点:
    • 组件化、声明式编码;虚拟 DOM + Diffing 算法,减少 DOM 操作
    • 可使用 React Navite 开发移动端
jsx
// hello word
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<h1>Hello, world!</h1>);

基础使用

CDN 引入

  • <script>标签的 type 类型,默认为 text/javascript;jsx 应使用text/babel
  • 引入顺序: react 核心 --> react-dom --> babel
html
<body>
  <!-- 准备一个“容器” -->
  <div id="test"></div>

  <!-- 引入react核心库 -->
  <script
    crossorigin
    src="https://unpkg.com/react@16/umd/react.development.js"
  ></script>
  <!-- 引入react-dom,用于支持react DOM 操作 -->
  <script
    type="text/javascript"
    crossorigin
    src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
  ></script>
  <!-- 引入bable.js,用于将jsx转为js -->
  <script src="../js/bable.min.js"></script>

  <!-- 书写jsx内容  type类型必须为 text/babel -->
  <script type="text/babel">
    // 创建虚拟DOM,此处不能写引号,他不是字符串而是虚拟DOM
    const VDOM = <h1>hello,word</h1>;
    // 渲染虚拟DOM到页面 ReactDOM.render(虚拟DOM,html容器)
    ReactDOM.render(VDOM, document.getElementById("test"));
  </script>
</body>

VSCode 插件

ES7+React/Readux...

  • 快捷键:rfc 函数组件、rcc 类式组件、imp、imn 引入模块、样式

常用插件库

  1. react-router-dom

    • 有三个版本,web 版 移动版 通用版【当前笔记只记录 web 版】
  2. prop-types 类型限制

  3. qs 路径字符处理

  4. redux 非官方

    • redux
    • redux-thunk 中间件(封装的函数),用于支持异步 action 的中间件
  5. react-redux 与 redux 功能一致

    • react 插件库,官方出品
  6. yarn add redux-thunk 使用 redux 的异步 action

    image-20220817171740969

Fragment

  • 相当于 vue 中的 template 标签,不会显示在页面上
    1. 用于在外层包裹标签,也可以写个空标签,作包裹
      • <Fragment></Fragment>
      • <></>
    2. 使用时必须先引入 import React,{Component,Fragment} from react

Context

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信;在应用开发中一般不用 context, 一般都用它的封装 react 插件

jsx
1) 创建Context容器对象:
	const XxxContext = React.createContext()

2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
    // 这个数据可以是数组、对象、字符串。。。。 {{}}   {[]}  {'xxx'}
	(<xxxContext.Provider value={数据}>
		子组件
    </xxxContext.Provider>)

3) 后代组件读取数据:

	//第一种方式:仅适用于类组件
	  static contextType = xxxContext  // 声明接收context
	  this.context // 读取context中的value数据

	//第二种方式: 函数组件与类组件都可以(直接写在子孙组件中)
	  (<xxxContext.Consumer>
	    {
	      value => ( // value就是context中的value数据
	      	return  要显示的内容
	      )
	    }
	  </xxxContext.Consumer>)

杂项

  • render 结构的注释

    • 借助花括号中是 js 表达式,可使用 js 的 /_ ... _/ 进行注释
  • jsx 标签样式中,可以使用{},插入 js 表达式

    jsx
    <li style={{ backgroundColor: this.state.mouse ? "#ddd" : "white" }}></li>
  • jsx 中可能不认识confirm,使用时需要加 window.confirm('确定吗')

  • 三元表达式的嵌套

    • image-20220714144109696
  • todos 案例

    • default 只在第一次生效,后续更改数据不再变化
    • checked 会让按钮固定值,需要配合 change 事件使用
    • image-20220714112411610

核心知识

虚拟 DOM

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

JSX 文件

  • 语法:
    • 定义虚拟 DOM 时,不要写引号
    • 标签中混入 JS 表达式时需要使用 {}
    • 样式的类名应使用 className 而不是 class,避免与 class 类的定义混淆
    • 多个单词的样式,采用驼峰命名法
    • 外层花括号表示是一个 js 表达式,内层括号表示是一个对象
    • 最外层只能有一个根标签,所有标签必须闭合
    • 标签首字母
      • 若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错!
      • 若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错!

说明:

  1. 不能直接被浏览器解析,需要借助babel.js将 jsx 转为 js

  2. JSX 也是一个表达式,编译后会被转为普通 JavaScript 函数调用,并且对其取值后得到 JavaScript 对象,本质就是一个 js 对象。

    js
    // 使用js语句创建虚拟DOM,而不用jsx
    const VDOM = React.creatElement("h1", { id: "title" }, "123");
    // 等同于 jsx 下的如下代码
    const VDOM = <h1 id="title"> 123 </h1>;
    
    // 使用js语句创建虚拟DOM,而不用jsx
    const VDOM = React.creatElement(
      "h1",
      { id: "title" },
      React.creatElement("span", { id: "title" }, 123)
    );
    // 等同于 jsx 下的如下代码
    const VDOM = (
      <h1 id="title">
        <span>123</span>
      </h1>
    );
  3. js 表达式与 js 语句的区别

    • 表达式:一个表达式会产生一个值,可以放在任何需要的地方
      • a+b demo() arr.map() function test(){}
    • 语句(代码),不能混入 jsx 虚拟 DOM 中
      • if() for(){} switch()

使用:

  • 模板中嵌入表达式

    • 花括号内可以放置任何有效的 JavaScript 表达式
    jsx
    const name = "Josh Perez";
    // 模板中使用 {花括号} 插入数据
    const element = <h1>Hello, {name}</h1>;
    
    function formatName(user) {
      return user.firstName + " " + user.lastName;
    }
    const user = {
      firstName: "Harper",
      lastName: "Perez",
    };
    
    // 模板中使用 {花括号} 插入函数执行的结果
    const element = <h1>Hello, {formatName(user)}!</h1>;
  • if 语句和 for 循环的代码块中使用 JSX,将 JSX 赋值给变量,把 JSX 当作参数传入,以及从函数中返回 JSX;但不能在虚拟 DOM 中使用这些语句。

    jsx
    function getGreeting(user) {
      if (user) {
        return <h1>Hello, {formatName(user)}!</h1>;
      }
      return <h1>Hello, Stranger.</h1>;
    }
  • 循环遍历渲染

    • 每一项都需要有唯一的标识,key 值与 vue 中的类似
    • 不能在 jsx 中使用 for if 等语句
    jsx
    const data = ["abc", "123", "456"];
    const VDOM = (
      <div>
        <h1>列表渲染</h1>
        <ul>
          {data.map((item, index) => {
            return <li key="index">{item}</li>;
          })}
        </ul>
      </div>
    );

组件分类

函数式组件(无状态)
  • 函数式组件中,this 默认指向 undefined,
    • 原因:babel 编译时,执行 js 严格模式,影响 this 执行
  • 组件首字母必须大写,否则会被解析为 html 标签,导致报错!
  • 函数式组件 必须有返回值
  • render() 函数中,写函数标签,并且首字母大写!
  • 执行 ReactDOM.render(......)的过程
    1. React 解析组件标签,找到 MyComponent 组件
    2. 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
jsx
// 1.创建函数式组件(组件名就是函数名)
function MyComponent() {
  console.log(this); // undefined
  return <h2>函数自定义组件(适用于【简单组件】)</h2>;
}
// 2.渲染组件到页面
ReactDOM.render(<MyComponent />, document.getElementById("test"));
类式组件(有状态)
  • 注意:
  1. 必须写 render 函数,且必须有返回值
  • 执行 ReactDOM.render(......)的过程
    1. React 解析组件标签,找到 MyComponent 组件
    2. 发现组件是使用类定义的,随后 new 这个类的实例,并通过该实例调用到原型上的 render 方法
    3. 将 render 返回的虚拟 DOM 转为真实 DOM,随后呈现在页面中
  • render 函数中,this 指向当前组件实例对象
jsx
// 1.创建类式组件  继承自 React.Component
class Welcome extends React.Component {
  // render 放在 Welcome的原型对象上(Welcome组件实例对象),供实例使用
  render() {
    return <h1>类式组件,适用于【复杂组件】的定义</h1>;
  }
}
// 渲染组件到页面
ReactDOM.render(<Welcome />, document.getElementById("test"));
非受控组件(现用现取)
  • 内容现用现取,不经过 state 管理
  • 过于依赖视图,频繁使用 ref
jsx
//创建组件
class Login extends React.Component {
  handleSubmit = (event) => {
    event.preventDefault(); // 阻止默认事件 表单提交
    const { username, password } = this;
    alert(
      `你输入的用户名是:${username.value},你输入的密码是:${password.value}`
    );
  };
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        用户名:
        <input ref={(c) => (this.username = c)} type="text" name="username" />
        密码:
        <input
          ref={(c) => (this.password = c)}
          type="password"
          name="password"
        />
        <button>登录</button>
      </form>
    );
  }
}
//渲染组件
ReactDOM.render(<Login />, document.getElementById("test"));
受控组件
  • 经过 state 管理的数据,减少 ref 的使用
jsx
//创建组件
class Login extends React.Component {
  //初始化状态
  state = {
    username: "", //用户名
    password: "", //密码
  };
  //保存用户名到状态中
  saveUsername = (event) => {
    this.setState({ username: event.target.value });
  };
  //保存密码到状态中
  savePassword = (event) => {
    this.setState({ password: event.target.value });
  };
  //表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault(); //阻止表单提交
    const { username, password } = this.state;
    alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
  };
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        用户名:
        <input onChange={this.saveUsername} type="text" name="username" />
        密码:
        <input onChange={this.savePassword} type="password" name="password" />
        <button>登录</button>
      </form>
    );
  }
}
//渲染组件
ReactDOM.render(<Login />, document.getElementById("test"));
  • 函数柯里化方式,简化上面代码,核心如下:
    • saveFormData 函数:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
jsx
class Login extends React.Component {
  //保存表单数据到状态中
  saveFormData = (dataType) => {
    return (event) => {
      this.setState({ [dataType]: event.target.value });
    };
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        用户名:
        <input
          onChange={this.saveFormData("username")}
          type="text"
          name="username"
        />
        密码:
        <input
          onChange={this.saveFormData("password")}
          type="password"
          name="password"
        />
        <button>登录</button>
      </form>
    );
  }
}
  • 不使用柯里化方式实现
jsx
class Login extends React.Component {
  // 保存表单数据到状态中  同时接收需要的参数,进行状态设置
  saveFormData = (dataType, event) => {
    this.setState({ [dataType]: event.target.value });
  };

  // 传递时: onChange 函数接收返回值中调用 this.saveFormData 并传入所有参数,会立即执行
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        用户名:
        <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>
    );
  }
}

组件实例核心属性

state 状态/数据
  • state 是组件对象最重要的属性,值是对象(可以包含多个 key-value 的组合)
  • 组件被称为"状态机",通过更新组件 state 来更新对应页面的显示(重新渲染组件)
  • 复杂写法:在创建组件时配置 constructor
    • 必须调用 super 方法
    • 通过 this.state ={},配置状态数据
jsx
// 1.创建类式组件  继承自 React.Component
class Welcome extends React.Component {
  constructor(props) {
    // 必须调用super,可以传入props
    super(props);
    this.state = {
      isHot: true,
    };
  }
  // render 放在 Welcome的原型对象上(Welcome组件实例对象),供实例使用
  render() {
    console.log(this); // this指向当前组件实例对象
    return <h1>今天天气:{this.state.isHot ? "炎热" : "寒冷"}</h1>;
  }
}
// 渲染组件到页面
ReactDOM.render(<Welcome />, document.getElementById("app"));

简写

  • class 中可以直接写赋值语句,且默认会放在类的实例对象自身

  • 因此 state 可以不放在 constructor 中,直接放在 class 类中

  • 类中的 this 默认指向组件的实例对象,再结合箭头函数,可简化方法的书写

    jsx
    class Weather extends React.Component {
      //初始化状态
      state = { isHot: false, wind: "微风" };
      render() {
        const { isHot, wind } = this.state;
        return (
          <h1 onClick={this.changeWeather}>
            今天天气很{isHot ? "炎热" : "凉爽"},{wind}
          </h1>
        );
      }
    
      //自定义方法 ———— 使用赋值语句的形式+箭头函数
      changeWeather = () => {
        const isHot = this.state.isHot;
        this.setState({ isHot: !isHot });
      };
    }
事件绑定
  • 原生方法

    • onclick、addEventListener,都可以正常使用,jsx 支持 js

    image-20220712164918998image-20220712165008439

    jsx
  • 使用 React 绑定事件

    1. 为标签绑定时,事件名使用驼峰命名法(例: onClick )
    2. 事件名写为函数形式,不能是原生的字符串形式,而是花括号的 js 表达式,且不能带小括号立即执行!
    3. this 指向问题:
      • constructor 中的 this 指向 当前组件实例对象
      • render 中的 this 指向 当前组件实例对象
      • 类的普通方法 中的 this 指向 undefined
    4. 执行次数
      • constructor 构造器调用几次? ——— 1 次
      • render 调用几次? —— 1+n 次 1 是初始化的那次 n 是状态 setState 更新的次数
      • 自定义函数调用几次? —— 调用几次执行几次
    5. 修改状态
      • 状态(state)不可直接更改,借助 React 的 API 进行更改,且更新是一种合并,不是替换。
      • this.state.isHot = !isHot //这是错误的写法
      • this.setState({ isHot: !this.state.isHot });
    jsx
    //1.创建组件
    class Weather extends React.Component {
      constructor(props) {
        super(props);
        //初始化状态
        this.state = { isHot: false, wind: "微风" };
        //解决changeWeather中this指向问题
        this.changeWeather = this.changeWeather.bind(this);
      }
    
      //render调用几次? ———— 至少一次,每次状态更新都再次调用
      render() {
        // 读取状态
        const { isHot, wind } = this.state;
        return (
          // 这里的this.changeWeather是constructor上的changeWeather
          <h1 onClick={this.changeWeather}>
            今天天气很{isHot ? "炎热" : "凉爽"},{wind}
          </h1>
        );
      }
    
      changeWeather() {
        //changeWeather放在哪里? ———— Weather的原型对象上,供实例使用
        //由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
        //类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
        this.setState({ isHot: !this.state.isHot });
        console.log(this);
      }
    }
    //2.渲染组件到页面
    ReactDOM.render(<Weather />, document.getElementById("test"));
setState 修改数据
  1. 函数式 setState
  2. 对象式的 setState
jsx
1. setState(stateChange, [callback])------对象式的setState(是函数式的语法糖)
       (1).stateChange为状态改变对象(该对象可以体现出状态的更改)
				this.staState({key:this.state.key+1},()=>{ console.log(this.state.key) })
       (2).callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
			 回调函数中可以确保获取到最新的数据

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

3.使用原则:
		(1).如果新状态不依赖于原状态 ===> 使用对象方式
		(2).如果新状态依赖于原状态 ===> 使用函数方式
		(3).如果需要在setState()执行后获取最新的状态数据,
					要在第二个callback函数中读取
props 基本使用
  • 在组件标签中 书写属性,配置值进行数据传递
    • age={18} 如果在标签中传递数字时,使用花括号包裹,不能写成引号字符串
  • 在组件的 props 上可以得到对应数据
  • props 是只读属性,如果对 props 进行修改会报错!
jsx
//创建组件
class Person extends React.Component {
  render() {
    // console.log(this);
    const { name, age, sex } = this.props;
    // props是只读的
    // this.props.name = 'jack' //此行代码会报错,因为props是只读的
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age + 1}</li>
      </ul>
    );
  }
}

//渲染组件到页面
ReactDOM.render(
  <Person name="jerry" age={19} sex="男" />,
  document.getElementById("test1")
);
ReactDOM.render(
  <Person name="tom" age={18} sex="女" />,
  document.getElementById("test2")
);
const p = { name: "老刘", age: 18, sex: "女" };
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))

// 这里的 {...p} 是js表达式的意思,react提供支持进行展开,原生js不支持直接展开
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));
ReactDOM.render(
  <Person name="tom" age={18} />,
  document.getElementById("test3")
);

函数式组件使用 props

jsx
//创建组件
function Person(props) {
  const { name, age, sex } = props;
  return (
    <ul>
      <li>姓名:{name}</li>
      <li>性别:{sex}</li>
      <li>年龄:{age}</li>
    </ul>
  );
}
Person.propTypes = {
  name: PropTypes.string.isRequired, //限制name必传,且为字符串
  sex: PropTypes.string, //限制sex为字符串
  age: PropTypes.number, //限制age为数值
};
//指定默认标签属性值
Person.defaultProps = {
  sex: "男", //sex默认值为男
  age: 18, //age默认值为18
};
//渲染组件到页面
ReactDOM.render(<Person name="jerry" />, document.getElementById("test1"));
props 限制
  • 在 React15 及以前,可以通过React.PropTypes限制传递的数据类型

  • 在 React16 之后,通过引入外部文件 prop-types 实现,减少 React 自身的体积

    • defaultProps 默认值限制(键值对形式存在)
    • propTypes 类型、必要性限制
      • isRequired 必传值
      • string 字符串
      • func 函数
      • number 数字
  • 组件化开发中

    1. 需要手动下载 prop-types这个库 yarn add prop-types

      js
      // 引入 PropTypes
      import PropTypes from 'prop-types'
      
      //对接收的props进行:类型、必要性的限制
      static propTypes = {
      	addTodo:PropTypes.func.isRequired
      }
jsx
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script>

<script type="text/babel">
// Person是创建的class类名
// 对标签属性进行类型、必要性的限制
Person.propTypes = {
	name:PropTypes.string.isRequired, //限制name必传,且为字符串
	sex:PropTypes.string,//限制sex为字符串
	age:PropTypes.number,//限制age为数值
	speak:PropTypes.func,//限制speak为函数
}
// 指定默认标签属性值
Person.defaultProps = {
	sex:'男',//sex默认值为男
	age:18 //age默认值为18
}
// 渲染组件到页面
ReactDOM.render(<Person name={100} speak={speak}/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
function speak(){
	console.log('我说话了');
}
</script>
props 简写
  • 将类型限制放在定义类中,借助 static 配置类的静态属性
    • 可以直接将 内容放在类自身而不是原型上
  • 构造器是否接收 props,是否传递给 super,取决于:是否希望在构造器中通过 this.props 访问 props
jsx
import PropTypes from "prop-types";
//创建组件
class Person extends React.Component {
  constructor(props) {
    //构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
    // console.log(props);
    super(props);
  }
  //对标签属性进行类型、必要性的限制
  static propTypes = {
    name: PropTypes.string.isRequired, //限制name必传,且为字符串
    sex: PropTypes.string, //限制sex为字符串
    age: PropTypes.number, //限制age为数值
  };
  //指定默认标签属性值
  static defaultProps = {
    sex: "男", //sex默认值为男
    age: 18, //age默认值为18
  };

  render() {
    const { name, age, sex } = this.props;
    return (
      <ul>
        <li>姓名:{name}</li>
        <li>性别:{sex}</li>
        <li>年龄:{age + 1}</li>
      </ul>
    );
  }
}
ReactDOM.render(<Person {...p} />, document.getElementById("test3"));
三类 ref
String 型 refs(16.3 前)
  • 与 vue 中 ref 类似,通过 ref 给标签打标识,再借助 refs 可以获取

  • ref 拿到的是真实 DOM

  • 存在效率上的问题,不建议使用,后期版本可能删除

    jsx
    //创建组件
    class Demo extends React.Component {
      //展示左侧输入框的数据
      showData = () => {
        const { input1 } = this.refs;
        alert(input1.value);
      };
      //展示右侧输入框的数据
      showData2 = () => {
        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.showData2}
              type="text"
              placeholder="失去焦点提示数据"
            />
          </div>
        );
      }
    }
回调型 refs
  • React 会自动调用 ref 身上绑定的函数,
    • 接收到一个参数,该参数为标签自身
  • 借助箭头函数,将该标签绑定到组件身上,再使用
  • 数据更新时的细节(无关紧要)
    • 当数据刷新时,会重新调用 render 函数,此时通过 ref 绑定的回调会被执行两次
    • 第一次返回 undefined;第二次返回真正的结构
    • 解决:将函数写在 class 类中,就会不重新执行回调绑定,影响不大
jsx
//创建组件
class Demo extends React.Component{
	//展示左侧输入框的数据
	showData = ()=>{
		const {input1} = this
		alert(input1.value)
	}
	//展示右侧输入框的数据
	showData2 = ()=>{
		const {input2} = this
		alert(input2.value)
	}
   // ref绑定函数外置
   saveInput = (c)=>{
		this.input1 = c;
		console.log('@',c);
	}
	render(){
		return(
			<div>
             <!-- c => this.input1 = c 相当于  (c)=>{this.input1 = c} -->
            {/*<input ref={c => this.input1 = c } type="text" placeholder="点击按钮提示数据"/>*/}&nbsp;
             	 <input ref={this.saveInput} type="text"/><br/><br/>
				<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
				<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>&nbsp;
			</div>
		)
	}
}
//渲染组件到页面
ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
React.createRef() 16.3 之后
  • 一个容器只能存一个标签,重复使用会把上一次的内容覆盖

  • 使用步骤

    1. myRef = React.createRef() 创建容器
    2. ref={this.myRef2} 在标签中绑定 ref
    3. this.myRef.current 拿到绑定的标签
    jsx
    //创建组件
    class Demo extends React.Component {
      // React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的
      myRef = React.createRef();
      myRef2 = React.createRef();
      //展示左侧输入框的数据
      showData = () => {
        alert(this.myRef.current.value);
      };
      //展示右侧输入框的数据
      showData2 = () => {
        alert(this.myRef2.current.value);
      };
      render() {
        return (
          <div>
            <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
            &nbsp;
            <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
            <input
              onBlur={this.showData2}
              ref={this.myRef2}
              type="text"
              placeholder="失去焦点提示数据"
            />
            &nbsp;
          </div>
        );
      }
    }
    //渲染组件到页面
    ReactDOM.render(<Demo a="1" b="2" />, document.getElementById("test"));
事件处理
  1. 通过 onXxx 属性指定事件处理函数(注意大小写)

    • React 使用的是自定义(合成)事件, 而不是使用的原生 DOM 事件 ——— 为了更好的兼容性
    • React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ———为了更高效
  2. 可通过 event.target 拿到发生事件的 DOM 元素对象 ——— 不要过度使用 ref

    jsx
    class Demo extends React.Component {
      //展示右侧输入框的数据
      showData = (event) => {
        alert(event.target.value);
      };
      render() {
        return (
          <div>
            <input
              onBlur={this.showData}
              type="text"
              placeholder="失焦提示数据"
            />
          </div>
        );
      }
    }
高阶函数
  • 在标签上绑定事件,并希望传入参数时,需要使用高阶函数 或直接在标签中传入回调函数,回调函数中再返回函数

    • 方法 1: <button onClick={()=>this.handleDelete(id)}>删除</button>

    • 函数返回函数的形式

      image-20220714103032219

生命周期

旧版本 17.x 以前
  • constructor class 类自身的构造器

  • componentWillMount 组件将要挂载之前

  • render 挂载组件(初始化渲染 或 更新渲染 时调用)

  • componentDidMount 组件

  • 挂载完毕

    • 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  • componentWillUnmount 组件将要卸载

    • 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
  • componentWillReceiveProps 组件将要接受新的数据时( 写在子组件 )

    • 坑:第一次页面渲染时不会触发该 生命周期钩子
    • 可以接受一个参数 props,即传递的数据形成的对象
  • forceUpdate() 强制更新

    • 当不更改任何状态中的数据时,更新一次界面
  • 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

    • 必须先引入 import ReacrDOM from 'react-dom'
  • setState()或父组件触发render() 状态更新阶段

    1. shouldComponentUpdate 控制状态更新的“阀门”
      • 默认不写该钩子,返回值一直为 true,允许状态更新
      • 返回值为 false 时,阻止状态更新
    2. componentWillUpdate 组件将要更新
    3. componentDidUpdate 组件更新完毕
      • 可接收三个参数
      • 参数 1:之前的 props 值
      • 参数 2:之前的 state 值
      • 参数 3:接收getSnapshotBeforeUpdate的返回值
jsx
//构造器
constructor(props){
	console.log('Count---constructor');
	super(props)
	//初始化状态
	this.state = {count:0}
}
//加1按钮的回调
add = ()=>{
	//获取原状态
	const {count} = this.state
	//更新状态
	this.setState({count:count+1})
}
//卸载组件按钮的回调
death = ()=>{
	ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
//强制更新按钮的回调
force = ()=>{
	this.forceUpdate()
}

//若state的值在任何时候都取决于props,那么可以使用getDerivedStateFromProps
static getDerivedStateFromProps(props,state){
	console.log('getDerivedStateFromProps',props,state);
	return null
}
//在更新之前获取快照
getSnapshotBeforeUpdate(){
	console.log('getSnapshotBeforeUpdate');
	return 'atguigu'
}
//组件挂载完毕的钩子
componentDidMount(){
	console.log('Count---componentDidMount');
}
//组件将要卸载的钩子
componentWillUnmount(){
	console.log('Count---componentWillUnmount');
}
//控制组件更新的“阀门”
shouldComponentUpdate(nextProps,nextState){
    console.log(this.props,this.state);  // 当前的props和state
    console.log(nextProps,nextState); // 接下来要变化的目标props和目标state
    // return !this.state.xxx===nextState  //可根据值得变化控制是否掉 render函数
	return true
}
//组件更新完毕的钩子
componentDidUpdate(preProps,preState,snapshotValue){
	console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
}

image-20220713152404791

image-20221020170540329

新旧版本区别
  1. 不推荐,但没删除的生命周期钩子
    • componentWillMount
    • componentWillReceiveProps
    • componentWillUpdate
    • 这些生命周期方法经常被误解和滥用;17+版本使用时添加 “UNSAFE_” 前缀。( “unsafe” 表示在 React 的未来版本中可能出现 bug,尤其是在启用异步渲染之后)
  2. 新增两个生命周期钩子(不常用)
    • getDerivedStateFromProps
    • getSnapshotBeforeUpdate
新增钩子
  1. static getDerivedStateFromProps(props, state)

    • 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用
    • 必须是静态函数,加 static
    • 返回一个对象来更新 state,如果返回 null 则不更新任何内容
    • 该钩子会导致代码冗余,并使组件难以维护
    jsx
    // 当 state的值在任何时候都取决于 props 时适用
    static getDerivedStateFromProps(props, state){
       // props 是标签传递的数据,state是状态数据
       return props
    }
  2. getSnapshotBeforeUpdate

    在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

    • 必须有返回值,除了 undefined,可以是任何类型
    • 此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
    jsx
    // 需求:新闻条数不断增加,但不会向下滚动
    class NewsList extends React.Component {
      state = { newsArr: [] };
      componentDidMount() {
        setInterval(() => {
          //获取原状态
          const { newsArr } = this.state;
          //模拟一条新闻
          const news = "新闻" + (newsArr.length + 1);
          //更新状态
          this.setState({ newsArr: [news, ...newsArr] });
        }, 1000);
      }
      getSnapshotBeforeUpdate() {
        return this.refs.list.scrollHeight;
      }
      componentDidUpdate(preProps, preState, height) {
        this.refs.list.scrollTop += this.refs.list.scrollHeight - height;
      }
      render() {
        return (
          <div className="list" ref="list">
            {this.state.newsArr.map((n, index) => {
              return (
                <div key={index} className="news">
                  {n}
                </div>
              );
            })}
          </div>
        );
      }
    }
    ReactDOM.render(<NewsList />, document.getElementById("test"));

image-20220713173445538

image-20230329163249492

Diffing 算法

  • 比较切换的最小粒度:标签
  • key 值的作用,与 Vue 中 key 一致

进阶学习

脚手架使用

  • 特点:

    1. 包含所有需要的配置(语法检查、jsx 编译、devServer...)
    2. 默认下载好了所有相关依赖
    3. 整体技术架构:react+webpack+es6+eslint
    4. 实现:模块化、组件化、工程化
  • 安装脚手架库

    1. 全局安装:npm install create-react-app -g
    2. 创建项目:create-react-app 项目名
    3. 进入项目文件下
    4. 启动项目:npm start
  • 常见命令

    shell
    yarn start    #运行项目
    yarn build    #打包项目
    yarn eject    #暴露webpack的命令,不可逆的;默认是隐藏的
  • 脚手架项目结构

    • public 文件夹
      • index.html 唯一 html 页面
      • favicon.ico 页签图标
      • manifest.json app 加壳的部分配置
      • robots.txt 爬虫规则文件
    • src
      • App.css App 样式文件
      • App.test.js App 测试文件,不常用
      • App.js App 组件
      • index.js 入口文件
      • index.css 公共样式文件
      • setupTests.js 用于做组件测试。。。
      • reportWebVitals.js 记录性能分析的文件。。。
  • 其他细节

    1. 引入 js 或 jsx 文件时,可不写后缀
html
<!-- index.html 单页面 -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <!-- %PUBLIC_URL%代表public文件夹的路径 -->
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <!-- 开启理想视口,用于做移动端网页的适配 -->
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) -->
    <meta name="theme-color" content="red" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <!-- 用于指定网页添加到手机主屏幕后的图标 -->
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!-- 应用加壳时的配置文件 -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <!-- 若浏览器不支持js则展示标签中的内容 -->
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>
js
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
// 引入App组件
import App from "./App";
// 引入记录性能分析的文件  。。。
import reportWebVitals from "./reportWebVitals";

const root = ReactDOM.createRoot(document.getElementById("root"));
// <React.StrictMode>标签包裹,可以用于检查代码中不合理的地方
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

组件编码流程

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

样式的模块化

  1. 将 css 文件名改为 xxx.module.css
  2. 引入时,采用 import 命名 from 'index.module.css'
  3. 使用时,{ 命名.类名 }
js
// 普通直接引入
import "./index.css";

// 模块化方式引入
// 1.将css文件名改为 xxx.module.css
// 2.引入时,采用 import hello from 'index.module.css'
// 3.使用时,{ hello.类名 }

组件间通讯

  • 父组件 -- 》 子组件

    • props

      1. 父组件中,在子组件标签上 命名={ 数据 }

        • 批量传递数据

          jsx
          <List {...this.state} /> // 传递所有state的数据
      2. 子组件中,通过this.props获取数据

  • 子组件 --》 父组件

    • 自定义事件( 状态在哪,操作数据的方法就在哪 )
      1. 父组件中定义事件,并传递给子组件
      2. 子组件中,通过this.props得到事件并在合适时机调用,传递数据
      3. 触发父组件中的自定义事件,拿到数据
  • 任意组件间传值

    • 消息订阅与发布插件: pub-sub.js、event 等
    • 集中式管理 : redux、dva 等
    • conText: 生产者-消费者模式(开发用的少,封装插件用的多)
  • 总结:

    js
    // 三种组件间关系  四种通信方式
    	1.props:
    		(1).children props
    		(2).render props
    	2.消息订阅-发布:
    		pubs-sub、event等等
    	3.集中式管理:
    		redux、dva等等
    	4.conText:
    		生产者-消费者模式
    
    
    // 推荐的搭配组合
    	父子组件:props
    	兄弟组件:消息订阅-发布、集中式管理
    	祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
  • 奇葩的父子组件间传值(插槽,可用于封装组件)

    • image-20221020121804059

ajax 请求

脚手架配置代理
  • 方法 1

    1. 优点:配置简单,前端请求资源时可以不加任何前缀。

    2. 缺点:不能配置多个代理。

    3. 工作方式:上述方式配置代理,当请求了 3000 不存在的资源时,那么该请求会转发给 5000 (优先匹配前端资源)

      json
      // 在package.json中追加如下配置
      "proxy":"http://localhost:5000"
  • 方法 2

  1. 第一步:创建代理配置文件

    js
    // 在src下创建配置文件:src/setupProxy.js
  2. 编写 setupProxy.js 配置具体代理规则:

    js
    const proxy = require("http-proxy-middleware");
    
    module.exports = function (app) {
      app.use(
        proxy(
          "/api", // 转发请求的前缀,遇到就转发代理
          {
            target: "http://localhost:3000/", // 转发地址
            pathRewrite: {
              "^/api": "", //去除该前缀,保证交给后台服务器的是正常请求地址(必须配置)
            },
            // changeOrigin 默认值为false 是真实的host地址,可能会存在问题
            changeOrigin: true, // 控制服务器收到的请求头的host(端口)值,用假值
            secure: false, // 是否验证证书
            ws: true, // 启用websocket
          }
        )
      );
      app.use(
        proxy("/edge", {
          target: "https://cpgxpt.zhengqiyezhi666.com:13001",
          changeOrigin: true,
          secure: false, // 是否验证证书
          ws: true, // 启用websocket
        })
      );
    };

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

路由 5.x

  • 安装命令:npm i react-router-dom@5

    • 2021.11 升级默认版本为 6,因此需要手动指定 5 版本
  • 理解:

    • 路由器 router 路由 route
    • react 的一个插件库,专门用于实现 SPA 应用
  • 潜在规则:

    • 路由组件 放在 page 文件夹下
    • 一般组件 放在 components 文件夹下
  • 路由组件 与 一般组件 区别

    • image-20220811143138571
  • 使用:

    1. 在 index.js 中引入 BrowserRouter ,包裹在<App>标签外

      jsx
      // 一个项目,只能由一个路由进行管理,通过 react-router-dom 分别暴露引入
      // 分为两种模式:  哈希 HashRouter       非哈希 BrowserRouter
      // 不管是 HashRouter,还是 BrowserRouter,底层都是 Router 组件。
      // 使用Router的history配置 设置路由模式:
      // 1. const createHistory = require("history").createBrowserHistory
      // 2. export default createHistory();
      // 3. <Router history={history}>
      
      // 更便捷的:
      import { BrowserRouter, Router } from "react-router-dom";
      ReactDOM.render(
        // <Router>
        <BrowserRouter>
          <App />
        </BrowserRouter>,
        // </Router>,
        document.getElementById("root")
      );

      image-20220811143332799

    2. 在页面中放置路由链接,切换组件

      • <Link/> 普通切换路由
      • <NavLink activeClassName='yangshi'/> 点击效果切换路由,点击谁就给谁 增加类名,默认为 active,也可以通过activeClassName自己指定
        1. 可以指定样式名
        2. 标签体内容是一个特殊的标签属性
        3. 通过 this.props.children 可以获取标签体内容
      jsx
      // 引入 Link标签,在 react-router-dom 上
      import {Link,Route} from 'react-router-dom';
      // 引入路由组件
      import About from '../About/About'
      
      // 使用link标签切换路由 ,编写路由链接
      // to为要跳转的路径
      <Link to='/about'>About</Link>
      <NavLink activeClassName='yangshi' to='/about'/>
      
      // 注册路由,显示的位置
      // path 指定当前组件显示的路径,  component 指定显示的路由组件
      <Route path='/about' component='{About}'/>
  • 标签体内容也是 props 的属性 children:'标签体内容'
  • 在标签上设置 children:'标签体内容' ;标签体内容会自动变成:标签体内容
jsx
import React, { Component } from "react";
import { NavLink } from "react-router-dom";

export default class MyNavLink extends Component {
  render() {
    // console.log(this.props);
    return (
      <NavLink
        activeClassName="atguigu"
        className="list-group-item"
        {...this.props}
      />
    );
  }
}
Switch 标签
  • 当 同一路径注册多个路由,又指向不同的组件时 <Router to='/about'/>

    • 默认路径匹配时:全部显示在界面上
    • 使用 Switch 标签包裹,可以阻止后续的再匹配,只显示第一个匹配的组件
    jsx
    // 引入Switch
    import { Switch, Route } from "react-router-dom";
    
    <Switch>
      <Route path="/about" component="{About}" />
      <Route path="/about" component="{About}" />
    </Switch>;
Redirect 标签
  • 当所有都不匹配时,指定显示的路由

    jsx
    // 引入Redirect
    import { Switch, Route, Redirect } from "react-router-dom";
    <Redirect to="/about" />;
解决多级路由样式丢失
  • 当使用多级路由,再刷新页面时可能出问题
  • image-20220714185100627
模糊匹配与严格匹配
  • 模糊匹配(默认)

    • 路由链接中写多级路由

    • 注册路由(挖的坑),只要从前往后有匹配的就算匹配上

    • 例: to='/home/a/b' path='/home' 可以匹配

      • to='/home' path='/home/a/b' 不能匹配
  • 严格匹配(非必要不要开启严格匹配

    • 必须完全一致时才算匹配,默认是模糊匹配

    • exact 属性开启严格匹配

      jsx
      <Route exact path="/home" component={Home} />
  • 注意:

    1. image-20220714190200325
嵌套路由

image-20220714191922378

路由传参
  • params

    • 暴露在地址栏

    • jsx
      // 1.路由链接(携带参数)
      <Link to={`/home/one/${age}/${name}`}>跳转</Link>
      // 2.注册路由时,声明接收
      <Route path='/home/one/:age/:name' component={Test}/>
      // 3.使用参数 在类的props身上
      const {name,age}=this.props.match.params
  • search

    • 暴露在地址栏

    • jsx
      // 1.路由链接(携带参数)
      <Link to={`/home/one/?age=${age}&name=${name}`}>跳转</Link>;
      // 2.无需在注册路由时声明接收
      // 3.接收使用search参数   ?age=18&name=value
      // 手动切割转化使用,或借助 qs(query-String) 插件的
      // qs.parse()方法  将URL解析成对象的形式
      // qs.stringify()将对象序列化成URL的形式,以&进行拼接
      const { search } = this.props.location;
  • state 参数

    • 不在地址栏显示,与组件的 state 无关

    • jsx
      // 1.路由传参, to传递一个对象
      // 外层花括号表示是js表达式,内层是个对象
      // pathname:'路由路径'    state:{key:value,...}
      <Link to={{pathname:'路由路径' state:{name:'value',age:18}}}>跳转</Link>
      // 2.接收使用参数 this.props.location.state
      // 当清除页面缓存后数据丢失,为避免报错,使用 || {}
      const {name,age} = this.props.location.state || {}
push 和 replace 模式
  • push 留下痕迹,是默认值

  • replace 替换,不保留当前界面

  • jsx
    <Link replace to={"/home/one"}>
      跳转
    </Link>
编程式路由导航
  • this.props.history.push('path 路径')

  • 携带参数方法与上面一致

    image-20220715104034169

一般组件使用路由
  • 上述所有路由相关 API 均属于路由组件独有,一般组件不具有

  • 如果希望在一般组件身上使用路由,需要引入使用 withRouter 函数,进行组件包装

    1. withRouter 返回一个新组件
    2. withRouter 加工一般组件,使其具备路由组件所特有的 API
  • jsx
    // 引入 withRouter函数
    import {withRouter} from "react-router-dom"
    // 定义一般组件
    class Header extends Component {...}
    // 包装一般组件并导出
    export default withRouter(Header)

lazy

路由组件的懒加载

jsx
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
// 注:fallback中的组件不能采用懒加载方式引入,必须使用原始引入方式
import Loading from "./Loading";
const Login = lazy(() => import("@/pages/Login"))(
  //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading组件
  <Suspense fallback={<Loading />}>
    <Switch>
      <Route path="/xxx" component={Xxxx} />
      <Redirect to="/login" />
    </Switch>
  </Suspense>
);

组件优化

  1. Component 的 2 个问题

    1. 只要执行 setState(),即使不改变状态数据, 组件也会重新 render() ==> 效率低

    2. 只要当前组件重新 render(), 就会自动重新 render 子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

  2. 原因:Component 中的 shouldComponentUpdate() 生命周期钩子总是返回 true

  3. 解决:

    js
    // 办法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.state.xxx===nextState  // 可根据值得变化控制是否掉 render函数
        }
    
    // 办法2:
    	使用PureComponent
    	PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
    // 注意:
    	只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
    	因此不要直接修改state数据, 而是要产生新数据
    
    
    // 项目中一般使用PureComponent来优化
    // 1.引入 PureComponent
    import React,{PureComponent} from 'react'
    // 2.使用PureComponent创建组件
    export default class Count extends PureComponent {
        xxx...
    }

错误边界

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

  • 特点:

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

  • 使用方式:

    js
    // getDerivedStateFromError 配合 componentDidCatch
    state={hasError:""}
    // 生命周期函数,一旦后代组件报错,就会触发
    static getDerivedStateFromError(error) {
        console.log(error);
        // 在render之前触发
        // 返回新的state
        return {
            hasError: true,
        };
    }
    
    componentDidCatch(error, info) {
        // 统计页面的错误。发送请求发送到后台去
        console.log(error, info);
    }

render props

向组件内部动态传入带有内容的结构(标签/组件)

jsx
Vue中:
	使用slot技术, 也就是通过组件标签体传入结构  <A><B/></A>
React中:
	使用children props: 通过组件标签体传入结构
	使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性


children props
  (<A>
    <B>xxxx</B>
  </A>)
  {this.props.children}
  // 问题: 如果B组件需要A组件内的数据, ==> 做不到

render props
(<A render={(data) => <C data={data}></C>}></A>)
 A组件: {this.props.render(内部state数据)}
 C组件: 读取A组件传入的数据显示 {this.props.data}

周边库

react-cookies

可设置失效时间。如果在浏览器端生成 Cookie,默认是关闭浏览器后失效 大小 4K 左右 每次都会携带在 HTTP 头中,如果使用 cookie 保存过多数据会带来性能问题 存在 XSS 注入的风险,只要打开控制台,就可以随意修改它们的值

js
// 下载依赖
cnpm install  react-cookies --save-dev
// 引入
import cookie from 'react-cookies'

cookie.save('userId', "123"); // 存
cookie.load('userId')    // 取
cookie.remove('userId')  // 删

// 设置失效时间
let inFifteenMinutes = new Date(new Date().getTime() + 24 * 3600 * 1000);//一天
cookie.save('userId', "123",{ expires: inFifteenMinutes });

// 补充:
名字相同cookie是可以同时存在的,cookie不仅有名字和值两个属性,还有域(domain)、路径(path)等属性,不同的域、不同的路径下可以存在同样名字的cookie。

Ant Design

js
// 1.安装
yarn add antd
// 2.看文档使用即可

状态管理

image-20230323170158799

Redux

image-20221020185710045

jsx
// API汇总

store.getState()   // 获取store管理的数据
store.dispatch({type:'increment',data:100})  // 触发组件的reducer事件,传入action对象 修改数据
store.subscribe(()=>{...}) // 检测redux中状态的变化,只要数据变化,就触发回调
  1. 安装 redux yarn add redux

  2. src 文件夹下创建 redux 文件夹,统一管理

  3. 使用说明

    • 整个应用只有一个store.js

      • 该文件专门用于暴露 一个 store 对象

      • // 引入 createStore 专门用于创建redux中最为核心的store对象
        import {createStore} from 'redux'
        // 引人为组件count 服务的reducer
        import countReducer from './count_reducer.js'
        // 生成store并暴露
        export default createStore(countReducer)
    • 每个组件有自己的 count_reducer.js

      • 该文件用于创建一个为 Count 组件服务的 reducer

      • jsx
        // reducer本质就是一个函数,并接受两个参数
        // 参数1:之前的状态preState
        // 参数2:动作对象action   {type:'things type',data:'things data'}
        
        // 初始化时默认  preState为undefined  action:{data为空,type为@@redux/INIT+随机字符串}
        export default function countReducer(preState = 0, action) {
          // 获取参数数据,执行操作,并return返回结果
          const { type, data } = action;
          switch (type) {
            case "increment":
              return preState + data;
            case "decrement":
              return preState - data;
            default:
              return preState;
          }
        }
    • 组件自身

      • // 引入store
        import store from '@/redux/store'
        // 获取store管理的数据
        store.getState()
        // 触发组件的reducer事件,传入action对象 修改数据
        store.dispatch({type:'increment',data:100})
action

避免手动去指定加减或其他操作,直接调用对应方法传入数据即可

  1. 在 redux 文件夹下 创建 count_action.js

    • 该文件专门为 Count 组件生成 action 对象

    • // 对象式 同步action
      export default const creatIncrement = data=>({type:'increment',data})
      export default const creatDecrement = data=>({type:'decrement',data})
      // 简写时,不能简写为,花括号会被认为函数的花括号
      const creatIncrement = data=>{type:'increment',data}
  2. 在组件自身

    • // 引入actionCreat对象,用于创建action对象
      import {creatIncrement,creatIncrement} from '@/redux/count_action.js'
      
      // 组件的事件中 调用dispatch并触发对应action方法
      increment =()=>{
          const value = this.selectNumber
          store.dispatch(creatIncrement(value))
      }
  3. 同步与异步 action

    • 同步 action 为 Object 对象 形式

    • 异步 action 为 Function 函数 形式

      • 必须借助 redux-thunk 中间件 yarn add redux-thunk

      • 使用步骤

        • store.js 文件

          // 在redux中额外引入applyMiddleware
          import {createStore,applyMiddleware} from "redux"
          // 引入redux-thunk 用于支持异步actioon
          import thunk from 'redux-thunk'
          // 引入为Count 组件服务的reducer
          import countReducer from './count_reducer'
          
          //暴露store
          export default createStore(countReducer,applyMiddleware(thunk))
        • crement_action 文件(该文件专门为 Count 组件生成 action 对象)

          // 函数式 异步action  返回值为一个函数
          export const createIncrementAsync = (data,time)=>{
              return (dispatch)=>{
                 dispatch(creatIncrement(data))
                 // store.dispatch(creatIncrement(data))   不需要单独引入store,可直接使用提供的参数
              }
          }
        • image-20221016174851718

存在的问题
  • 视图不更新(redux 中存在,但 react-redux 中不存在该问题)

    jsx
    /*
    存在问题:store.dispatch 修改状态后,不会触发视图更新
    解决方法:
      方案1:
      1.借助生命周期钩子,在组件挂载完毕componentDidMount后监视store变化
        2.使用this.steState({}) 会触发视图更新
    
      方案2:在index.js入口文件中进行全局监视
    */
    componentDidMount(){
        // 检测redux中状态的变化,只要数据变化,就调用render
        store.subscribe(()=>{
            this.setState({})
        })
    }
    
    // 在index.js入口文件中引入 store
    // 引入store
    import store from 'src/redux/store.js'
    // 检测redux中状态的变化,只要数据变化,就为App组件调用render,更新界面
    // 因为存在DOM diff算法 不会引起大面积的性能问题
    store.subscribe(()=>{
        React.render(<App>,document.getElementById('root'))
    })

react-redux

官方的 redux,react 自己的 新建 redux 文件夹

jsx
// 1.安装react-redux
yarn add react-redux
// 引入 ui组件  引入connect 用于连接UI组件与redux
import CountUI from './countUI.js'
import {connect} from 'react-redux'

// 2.创建并暴露容器组件
// 箭头函数默认返回一个对象,需要包一个小括号
//  mapStateToProps 简写
    state=>({nmb: state.a}),
//  mapDispatchToProps 传统写法(函数形式)
//    dispatch=>({
//        jia:number=> dispatch({type:'jia',data:number}),
//        jian:number=> dispatch({type:'jian',data:number}),
//  })

// 简写 react-redux 内部实现自动分发dispatch 对象形式
    {
        jia:{type:'jia',data:number},
        jian:{type:'jian',data:number}
    }
)(CountUI)

// 在组件中 使用store 并通过 props组件配置形式传入
import store from './store.js'
<Count store={store}/>
  • connect
    • 第一个括号中传入两个参数
      • 第一个参数为函数且返回值作为 状态 传递给 UI 组件
        • 参数 1 state:redux 中的 state
      • 第二个参数为函数且返回值作为 操作状态的方法 传递给 UI 组件
        • 参数:dispatch 方法,直接调用
    • 第二个括号中,传入 UI 组件
  • 注意:
    1. 容器组件中的 store 是靠 props 传进去的,而不是在容器组件中直接引入
    2. 第二个参数 mapDispatchToProps,也可以是一个对象
  • image-20221017164140722
默认将 store 传递给所有容器组件
  • jsx
    // 默认情况下,需要为所有的容器组件手动传入 store
    import store from './store.js'
    <Count store={store}/>
    
    // index.js 入口文件
    import React from 'react'
    import ReactDOM from 'react-dom'
    import App from './App'
    import store from './redux/store.js'
    // 借助 Provider标签自动传递
    import {Provider} from 'react-redux'
    
    ReactDOM.render(
      <Provider store={store}>
          <App/>
      <Provider/>,
        documnet,getElementById('root')
    )
  • 这里差了一些笔记,没有记录!

  • image-20221019164128419

redux-saga

  • 是一个redux中间件;一个用于管理应用程序 Side Effect(副作用,例如异步获取数据,访问浏览器缓存等)的 library,它的目标是让副作用管理更容易,执行更高效,测试更简单,在处理故障时更容易。
  • 处理异步内容
基础使用
// 1.安装
npm install --save redux-saga
Effect 方法

import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects' // 引入

// take 用来监听action,返回的是监听到的action对象
// 例:可以监听到UI传递到中间件的Action,上述take方法的返回,就是dispatch的原始对象。一旦监听到login动作,返回的action为:  {type:'login'}
const loginAction = { type:'login' }
dispatch(loginAction)  // UI组件中 dispatch一个action
const action = yield take('login');  // 在saga中使用

// call(apply) 主要用于异步请求,与js中的call和apply相似
// call方法调用fn,参数为args,返回一个描述对象。不过这里call方法传入的函数fn可以是普通函数,也可以是generator。call方法应用很广泛,在redux-saga中使用异步请求等常用call方法来实现。
call(fn, ...args)
yield call(fetch,'/userInfo',username)

// put 对应与redux中的dispatch,可以发出原始action
yield put({type:'login'})

// select 对应的是redux中的getState,用户获取store中的state
const state= yield select()

// fork方法 相当于web work,fork方法不会阻塞主线程,在非阻塞调用中十分有用

// takeEvery和takeLatest
// 用于监听相应的动作并执行相应的方法,是构建在take和fork上面的高阶api,比如要监听login动作
// takeEvery监听到login的动作,就会执行loginFunc方法,除此之外,takeEvery可以同时监听到多个相同的action。
takeEvery('login',loginFunc)

// takeLatest方法跟takeEvery是相同方式调用:
// 与takeEvery不同的是,takeLatest是会监听执行最近的那个被触发的action。
takeLatest('login',loginFunc)

image-20221022163516855

image-20221022163535201

mobx

  • https://www.mobxjs.com
  • 安装:yarn add mobx
  • 特点
    • 利用 getter 和 setter 来收集组建的数据依赖关系,从而在数据发生改变的时候精准重绘(类似 vue)
    • 写法上偏向面向对象 oop;可以多 store,并非单一 store
    • 不需要使用返回一个新数据,对一份数据直接进行修改
    • Redux 默认以原生 js 对象形式存储,而 mobx 使用可观察对象存储
  • 两种 react 绑定方式,
    • mobx-react-dom:仅支持函数组件
    • mobx-react:额外支持基于类式组件
  • 实践:
    • 可以在任何位置对 数据进行任意的修改(不统一,容易出错,不建议)
    • 最佳方式(使用严格模式,根据一定的规范化流程操作数据)
js
import React, { Component } from "react";
import { observableNumber, autorun } from "mobx";

// 对于普通类型数据的监听
// 定义普通数据 observable.box()
var Numbervalue = observable.box(10);
// 监听,会自动执行一次,之后每次值(autorun中使用到的值)被改变时会再触发 autorun
autorun(() => {
  console.log(Numbervalue.get());
});
// 修改数据
Numbervalue.set(20);

// 观察对象,通过map
const myobj = observable.map({ name: "redux", age: 100 });
autorun(() => {
  console.log("name属性改变才执行,age改变不执行", myobj.get("name"));
});
myobj.set("name", "mobx");

// 观察对象,不通过map,直接定义直接用
const myobj = observable({ key: "value" });
// 获取:myobj.key     修改:myobj.key='new value'

// 观察数组
const list = observable([1, 2, 4]);
list[2] = 3;
js
// 开启严格模式,保持代码书写的合规(不允许在store外部随便修改数据)
// 需要配置对应的修改方法,并把他们指定为action
import { observable, configure } from "mobx";
configure({
  enforceActions: "always", // 开启严格模式,如果在外部任意修改会报错提示
});
// 定义数据,在专门的store.js中
const store = observable(
  {
    isShow: true,
    changeShow() {
      this.isShow = true;
    },
    changeHide() {
      this.isShow = false;
    },
  },
  {
    changeShow: action, // 标记这两个方法是action,专门修改可观测的value
    changeHide: action,
  }
);
export default store;

// 在其他位置使用时
import store from "./xxx/xxx/store.js";
store.changeShow();
js
// 异步的 使用 runInaction
import { runInaction } from 'mobx'
class Store {
  getLIst(){
    axios({.....}).then(res=>{
      // 在执行getList时,axios会立即返回promise对象,并不会等待then回调,mobx严格模式下会报错

    })
  }
}
jsx
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
// 注:fallback中的组件不能采用懒加载方式引入,必须使用原始引入方式
import Loading from "./Loading";
const Login = lazy(() => import("@/pages/Login"))(
  //2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading组件
  <Suspense fallback={<Loading />}>
    <Switch>
      <Route path="/xxx" component={Xxxx} />
      <Redirect to="/login" />
    </Switch>
  </Suspense>
);
jsx
// 定义store  store/Timer.js
import {makeAutoObservable} from 'mobx'

export class Timer {
  secondPassed = 0;
  constructor(){
    makeAutoObservable(this);
  }
  increase(){
    this.secondsPasses+=1;
  }
  async decrement(){
    const res = await new Promise((resolve)=>{
      setTimeout(()=>{
        resolve(1)
      },500)
    })
    runInAction(()=>{
      this.secondsPasses -= res
    })
  }
  reset(){
    this.secondsPasses=0;
  }
}
export default new Timer();

// 使用
import {observer} from 'mobx-react-dom'
import Timer from './store/Timer'
functon TimeView(timer){
  return (
    <div>
      <h1>{timer.secondsPasses}</h1>
      <button onClick={()=>{ timer.increase }}>add</button>
      <button onClick={()=>{ timer.increase }}>add</button>
    </div>

  )
}

export default observer(TimerView)


// mobx中发送异步请求,使用runInAction包裹异步处理中的数据修改操作

image-20230321111730200

Hooks

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

state hook

Recoil

facebok 推出新一代 React 状态管理工具

  • 只能用在 react 体系框架中 preact react(其他框架体系中无法使用)
  • 不能在类组建中使用 Recoil(使用 hooks 实现)
  • Recoil 中的状态默认是 Immutable( Object.freeze() 不可更改)
  • 状态管理现状

    • redux
      • 集中式状态管理(单一数据流)
      • 学习成本陡峭(store / state / action / reducer )
      • 非官方(redux / redux-thunk / redux-saga / redux-promise )
    • recoil
      • 分散的状态管理( Atom / Selector )
      • 上手快( Hooks 语法 )
    • 区别:
      • redux 是树形结构的状态,当层级较深时,内部改变需要逐级传递,甚至可能造成不必要的外层节点更新影响性能

    image-20230324235258972

  • 最佳实践

    • atom (原子状态)

      • atom 是存储状态的最小单位

      • 副作用:当状态更新时执行的其他逻辑

      • Immutable 的优势(默认是不可变)

        • 降低 Mutable 带来的复杂度,避免被随意修改
        • 状态更新时,节省内存空间
        • 拥抱函数式编程

        image-20230325140322525

      • atomFamily 允许传参

    • selector 选择器 (衍生状态 | 计算状态)

      • selector 是以其他状态(atom | selector )为参数的纯函数
      • selectorFamily 允许传参
      • constSelector
      • errorSelector
      • get 方法,获取原子状态或其他状态
      • cachePolicy:选择缓存策略,目前不稳定
      • image-20230325140631354
    jsx
    // 安装
    npm install recoil
    
    // 使用
     - 初始状态 RecoilRoot
     - 定义状态 atom | selector
     - 使用状态	Recoil Hooks
    
    
    // 包裹组件,作为根外层
    import React from 'react';
    import { RecoilRoot, atom, selector } from 'recoil';
    
    function App() {
      return (
        <RecoilRoot>
          <CharacterCounter />
        </RecoilRoot>
      );
    }

没看的部分

tailwindcss