IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React(牛牛) -> 正文阅读

[JavaScript知识库]React(牛牛)

一、React

网址:https://reactjs.org/

1.1、概述
React 起源于 Facebook(脸书) 的内部项目,它是一个用于构建用户界面的 javascript 库,Facebook用它来架设公司的Instagram网站,并于2013年5月开源。
React 拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它。认为它可能是将来 Web 开发的主流工具之一。
1.2、特点
◆ 声明式
你只需要描述UI看起来是什么样式,就跟写HTML一样,React负责渲染UI
◆ 基于组件
组件时React最重要的内容,组件表示页面中的部分内容 – react元素 – 虚拟dom
◆学习一次,随处使用
使用React可以开发Web应用(React-dom),使用React可以开发移动端(react-native),可以开发VR应用(react 360)
1.3、浏览器扩展与vscode开发扩展安装
Chrome 浏览器扩展:

在这里插入图片描述

vscode安装react开发扩展
在这里插入图片描述

1.4、第一个react应用程序
react开发需要引入多个依赖文件,其中react.js、react-dom.js这两个文件是我们创建react应用程序必须要引入的依赖文件。

react.js 是核心,提供创建元素,组件等功能
https://unpkg.com/react@17/umd/react.development.js
react-dom.js 提供DOM相关功能
https://unpkg.com/react-dom@17/umd/react-dom.development.js

下载对应的react.js和react-dom.js的开发版本的js类库文件到本机中后,通过script引入到当前的网页中

// React 的核心库
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
//DOM 相关的功能
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>

在html中定义reactjs渲染容器id和进行React实例化相关操作,完成helloworld显示

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>测试一下</title>
</head>
<body>
  <div id="app"></div>
  <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
  <script>
    ReactDOM.render(React.createElement('div',{},'你好世界'),document.querySelector('#app'));
  </script>
</body>
</html>

在这里插入图片描述

在这里插入图片描述

二、JSX

2.1、简介
由于通过React.createElement()方法创建的React元素有一些问题,代码比较繁琐,结构不直观,无法一眼看出描述的结构,不优雅,开发时写代码很不友好。
React使用 JSX 来替代常规的JavaScript,JSX 可以理解为的JavaScript语法扩展,它里面的标签申明要符合XML规范要求。React不一定非要使用JSX,但它有以下优点:

 ★ JSX 执行更快,因为它在编译为JavaScript代码后进行了优化
	jsx是浏览不能够直接运行的,它需要一个转译器进行转译(babel),转为js
★ 它是类型安全的,在编译过程中就能发现错误
★ 声明式语法更加直观,与HTML结构相同,降低了学习成本,提升开发效率速
★ jsx语法中一定要有一个顶级元素包裹,否则编译报错,程序不能运行
	js != jsx  jsx => js+xml集合版本,js增强版,js有的它有,js没有它也有

2.2、重构helloworld
在项目中尝试 JSX 最快的方法是在页面中添加这个<script> 标签,引入解析jsx语法的babel类库,注意后续的script标签块中使用了jsx语法,则一定要申明类型 type="text/babel",否则babel将不进行解析jsx语法。

<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

重构helloworld

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>jsx重构helloworld</title>
</head>
<body>
  <div id="app"></div>
  <script src="./js/babel.min.js"></script>
  <script src="./js/react.development.js"></script>
  <script src="./js/react-dom.development.js"></script>
  <script type="text/babel">
  ReactDOM.render(
    <div>你好世界</div>,
    document.getElementById('app');
  );
  </script>
</body>
</html>

在这里插入图片描述

2.3、在 jsx 中语法中的 js 表达式
2.3.1、嵌入JS表达式

在jsx语法中,要把JS代码写到{ }中,所有标签必须要闭合。

let num = 100
let bool = false;

// JSX 语法
var myh1 = (
  <div>
    {/* 我是注释 */}
    {num}
    <hr />
{/* 三目运算 */}
{true ? "条件为真" : "条件为假"}
  </div>
)

在这里插入图片描述

2.3.2、属性绑定动态值

const title = '你好世界';
// 一定要会使用它,这样用法在工作中天天用
<button title={title}>按钮</button>

<style>
/* class 是es6定义类的关键词,在jsx中不能使用,只能通过 className来调用定义好的样式类 */
.btn{color:red;}
</style>
// classclassName一定要牢记,因为在工作,写样式就要用到
// for  htmlFor
<button className="btn">按钮</button>
#jsx中绑定样式 style 只绑定对象
<div style={ {color: 'red'} }></div>

#动态显示html元素  dangerouslySetInnerHTML 可以进行对html标签进行反转义
#在react17中jsx可以直接解析html,但是对于转义后的html还不会自动转义,需要使用它
const html = 你好 &copy;世界

#不转义html元素输出 {两个下划线html:html}
<div dangerouslySetInnerHTML={ {__html:html} }></div>

在这里插入图片描述

样式的属性绑定
在这里插入图片描述

2.3.3、jsx渲染数组列表

<script type="text/babel">
  let arr = ["张三","李四","王五","赵六"];
  let app = document.getElementById('app');
  ReactDOM.render(
<div>
  {/* jsx中如果是一维数组,直接写上就可以遍历渲染了 */}
      { arr }  
    </div>,
    app
  );
</script>

2.3.4、jsx渲染数组列表并处理

<script type="text/babel">
  let nameList = ['张三', '李四', '王五'];
  ReactDOM.render(
    <div>
      { 
	// 方案1
	nameList.map((item,index) => {
	return (<h3>{item}</h3>)
	});

	// 方案2 【推荐】
	nameList.map((item,index) => <h3>{item}</h3>);  
    </div>,
    document.getElementById('app')
  );
</script>

三、React脚手架

React团队主要推荐使用create-react-app来创建React新的单页应用项目的最佳方式。
React脚手架(create-react-app)意义

? 脚手架是官方推荐,零配置,无需手动配置繁琐的工具即可使用
? 充分利用 Webpack,Babel,ESLint等工具辅助项目开发
? 关注业务,而不是工具配置

create-react-app会配置你的开发环境,以便使你能够使用最新的avaScript特性,提供良好的开发体验,并为生产环境优化你的应用程序。你需要在你的机器上安装 Node >= 8.10 和 npm >= 5.6。

建议可以安装 js包管理工具 yarn (facebook开发的) — 速度会快一些
npm i -g yarn

3.1、创建React项目

npx create-react-app my-app // 创建项目,时间会有点的久
cd my-app // 进入到安装项目中
npm start // 启动项目

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

四、React 组件

在这里插入图片描述

4.1、简介
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 类或函数。它接受任意的入参(props),并返回用于描述页面展示内容的 React元素(jsx)。 定义好的组件一定要有返回值

react中定义组件的方式2种
	? 函数组件(无状态组件/UI组件)
	? 类组件(状态组件/容器组件)

4.2、组件的创建方式
4.2.1、函数创建组件

函数组件(无状态组件):使用JS的函数创建组件
函数名称必须以大写字母开头,
函数组件必须有返回值(jsx),表示该组件的结构,且内容必须有顶级元素

import React from 'react'
// 函数名首字母必须大写
function Hello() {
    return (
        <div>这是第一个函数组件</div>
    )
}

在这里插入图片描述

4.2.2、类组件

 使用ES6语法的class创建的组件(状态组件)
 类名称必须要大写字母开头
 类组件要继承React.Component父类,从而可以使用父类中提供的方法或者属性
 类组件必须提供 render 方法,用于页面结构渲染,结构必须要有顶级元素,且必须return去返回
import React from 'react'
// 创建class类,继承React.Component,在里面提供render方法,在return里面返回内容
class Hello extends React.Component{
    render(){
        return (
            <div>这是第一个类组件</div>
        )
    }
}

在这里插入图片描述

4.3、父子组件传值(props 【只读属性】)
组件间传值,在React中是通过只读属性 props 来完成数据传递的。

props:接受任意的入参,并返回用于描述页面展示内容的 React元素。
props中的数据是不能被修改的,只能读取。
react中的数据不是双向,单向数据流。

4.3.1、函数组件
在这里插入图片描述

4.3.2、类组件

class Cmp extends React.Component{
    render(){
      return (
        <div>{ this.props.name } -- 我是一个类</div>
      );
    }
}

在这里插入图片描述

五、React事件处理

5.1、事件绑定
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
?React 事件的命名采用小驼峰式,而不是纯小写。

onClick  onChange

?使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

onClick={this.fn} // 绑定实现方法一定要用{函数或方法}   // 注函数或方法不能用小括号

?类组件与函数组件绑定事件是差不多的,只是在类组件中绑定事件函数的时候需要用到this,代表指向当前的类的引用,在函数中不需要调用this

export default class extends React.Component {
    clickHandle(e){
        console.log('点了')
    }
    render(){
        return (
            <div><button onClick = {this.clickHandle}>点我点我点我</button></div>
        )
    }
}

类组件
在这里插入图片描述

函数组件
在这里插入图片描述

5.2、事件传值
☆ 函数组件
在这里插入图片描述

☆ 类组件
在这里插入图片描述

5.3、事件对象
React中可以通过事件处理函数的参数获取到事件对象,它的事件对象叫做:合成事件,即兼容所有浏览器,无需担心跨浏览器兼容问题,此事件对象还拥有和浏览器原生事件相同的接口,包括 stopPropagation()preventDefault(),如果你想获取到原生事件对象,可以通过 e.nativeEvent 属性来进行获取。
作用:获取dom元素或数据传值
react中的实现没有提供像vue中的事件修饰符,像冒泡和默认行为,需要开发者自行解决。

export default class extends React.Component {
    clickHandle(e){
        // 获取原生事件对象
        console.log(e.nativeEvent)
    }
    render(){
        return (
            <div><button onClick = {this.clickHandle}>点我点我点我</button></div>
        )
    }
}

类组件
在这里插入图片描述

如果使用柯里化函数绑定事件,event对象传递

在这里插入图片描述

5.4、this指向问题
在JSX事件函数方法中的 this,默认不会绑定 this指向。如果你忘记绑定,当你调用这方法的时候, this 的值为 undefined。所以使用时一定要绑定好this的指向。
? 构造方法中绑定

constructor(props){
    super(props)
    // 在构造方法中指定this指向  <button onClick={this.fun()}>按钮</button>
    this.fun = this.fun.bind(this)
}

? 申明是使用bind绑定

<button onClick={this.fun.bind(this)}>按钮</button>

? 箭头函数绑定

<button onClick={(evt) => this.fun(evt)}>按钮</button>

? 定义事件方法使用箭头函数来绑定

// 通过箭头函数定义事件方法,也能解决this指向问题
fn = (evt) => {
    alert(123);
}

在这里插入图片描述

六、State状态

state状态只在class类组件才有,函数组件没有此功能。
6.1、基本使用
◆ 状态(state)即数据,是组件内部的私有数据,只能在组件内部使用
◆ state的值是对象,表示一个组件中可以有多个数据
◆ 通过this.state来获取状态,react中没有做数据代理
◆ state数据值可以修改 this.setState
◆ state可以定义在类的构造方法中也可以写在类的成员属性中

export default class extends React.Component {
    constructor(props){
        super(props)
        // 第一种初始化方式
        this.state = {
            count : 0
        }
}
/*
    // 第二种初始化方式
    state = {
        count:1
}
*/
    render(){
        return (
            <div>计数器 :{this.state.count}</div>
        )
    }
}

6.2、修改状态
state中的值不能直接通过修改state中的值来进行修改数据操作,react提供一个this.setState方法来完成state数据的修改操作

setState() 作用:
	1.修改 state 
	2.更新UI
注:setState本身并不是异步,只是因为react的性能优化机制体现为异步。在react的生命周期函数或者作用域下为异步,在原生的环境下为同步。
总结:setState即是异步也是同步


语法1
this.setState({
    key:value
})

语法2  推荐
this.setState(state => {
    return {key:value}
})

##异步
state = {
    number:1
};
componentDidMount(){
this.setState({number:3},()=>{
    // 获取最新的修改数据
        console.log(this.state.number)
    })
}

##同步
// 在延时器中
state = {
    number:1
};
componentDidMount(){
    setTimeout(()=>{
      this.setState({number:3})
      console.log(this.state.number)
    },0)
}


// 在原生事件中
state = {
    number:1
};
componentDidMount() {
    document.body.addEventListener('click', this.changeVal, false);
}
changeVal = () => {
    this.setState({
      number: 3
    })
    console.log(this.state.number)
}

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

6.3、props与state区别
? props 中存储的数据,都是外界传递到组件中的
? props 中的数据,都是只读的
? state 中的数据,都是可读可写的
? props 在函数声明或类申明的组件中都有
? state 只有类申明的组件中才有

七、props进阶

7.1、children属性
children属性,表示组件标签的子节点,当组件标签有子节点时,props就会有该属性,与与普通的props一样,其值可以使任意类型。单标签和双标签中没有数据都是没有此属性。

// 父组件
class App extends React.Component {
  render() {
    return (
        <div>
          <Cmp>我是children中的值</Cmp>
        </div>
    )
  }
}

// 子组件
{props.children} //获取数据

在这里插入图片描述

7.2、类型限制(prop-types)
对于组件来说,props是外部传入的,无法保证组件使用者传入什么格式的数据,简单来说就是组件调用者可能不知道组件封装着需要什么样的数据,如果传入的数据不对,可能会导致程序异常,所以必须要对于props传入的数据类型进行校验。

? 安装校验包 npm i -S prop-types

# 在组件中导入
import PropTypes from 'prop-types'


# 函数组件
function App(){}
// 验证规则
App.propTypes = {
    prop-name:PropTypes.string
}

# 类组件
class App extends Component{
    // 类内部完成 检查
static propTypes = {
   prop-name:PropTypes.string
}
}
? 约束类型
https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#proptypes
- 类型: array、bool、func、number、object、string
- React元素类型:element
- 必填项:isRequired·‘’’
- 特定结构的对象: shape({})

? 类组件
在这里插入图片描述

? 函数组件
在这里插入图片描述

7.3、默认值(defaultProps)
如果props没有属性没有传过数据,为了不让程序异常,可以设置其默认值。

# 函数组件
function App(){}

App.defaultProps = {
    title: '标题'
}

# 类组件
class App extends Component {
static defaultProps = {
    title: '标题'
}
}

函数组件默认值
在这里插入图片描述

类组件默认值设置
在这里插入图片描述

八、表单处理

8.1、受控组件
将state与表单项中的value值绑定在一起,有state的值来控制表单元素的值,称为受控组件。
绑定步骤:
? 在state中添加一个状态,作为表单元素的value值
? 给表单元素绑定change事件,将表单元素的值设置为state的值

<input type="text" value={this.state.username} onChange={this.inputChange.bind(this)} />

//注:多表单元素需优化事件方法
this.setState({
    [e.target.name]: e.target.value
})

8.2、非受控组件
没有和state数据源进行关联的表单项,而是借助ref,使用元素DOM方式获取表单元素值
使用步骤
? 调用 React.createRef() 方法创建ref对象
? 将创建好的 ref 对象添加到文本框中
? 通过ref对象获取到文本框的值

class App extends React.Component {
  constructor(props){
    super(props)
    //创建 ref
    this.username = React.createRef()
  }
  // 获取文本框的值
  fn =() => {
    console.log(this.username.current.value)
  }
  render(){
    return (
        <div>
          <input type ="text" ref={this.username} />
          <button onClick ={this.fn}>获取值</button>
        </div>
    )
  }
}

九、生命周期

函数组件无生命周期,生命周期只有类组件才拥有。
生命周期函数指在某一时刻组件会自动调用并执行的函数。React每个类组件都包含生命周期方法,以便于在运行过程中特定的阶段执行这些方法。例如:我们希望在第一次将其呈现到DOM时设置一个计时器Clock。这在React中称为“安装”。我们也想在删除由产生的DOM时清除该计时器Clock。这在React中称为“卸载”。
参考:链接
完整的生命周期图
:

常用的生命周期图
在这里插入图片描述

? constructor(props)
React组件的构造函数在挂载之前被调用。在实现React.Component构造函数时,需要先在添加其它内容前,调用super(props),用来将父组件传来的props绑定到继承类中。
只调用一次

constructor(props) {
    super(props)
    
}

在这里插入图片描述

? static getDerivedStateFromProps(nextProps, prevState)
此方法是react16.3之后新增,会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
此方法适用于罕见的用例,即当前组件的 state 的值在任何时候都取决于 props传入。
state = {

    num: 0
};

render() {
    return <div>当前的num是{this.state.num}</div>;
}
// 从props中获取数据,绑定到当前的这个组件中的state
// nextProps 父组件传递过来的整个props对象
// prevState 当前组件中的状态对象state
static getDerivedStateFromProps(nextProps, prevState) { 
     // 不需要更新当前state
     return null 
}

? render()
render()方法是必需的,它主要负责组件的渲染,会被重复调用若干次
?componentDidMount
它会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
? shouldComponentUpdate(nextProps, nextState)
当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true则组件继续渲染,为false则当前组件不会渲染。首次渲染或使用 forceUpdate() 时不会调用该方法。此方法仅作为性能优化的方式而存在。你也可以考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。
当this.setState()修改了state中的数据后,当前组件将重新渲染,同时也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件)
在这里插入图片描述

shouldComponentUpdate(nextProps, nextState) {
    // 判断是否需要被渲染,如果需要则返回true,否则返回false 
    if (nextProps.b === this.props.b) {
        return false;
    } else {
        return true;
    }
}

// 简化方法:只需要将类组件的继承关系改成继承`PureComponent`即可,这样一来,框架会自动判断值是否有变化进一步决定组件是否需要被渲染。
import React, { PureComponent } from "react";
class Cmp extends PureComponent {
    render() {
        console.log("Cmp被渲染了");
        return <div>父亲传递过来的值b为:{this.props.b}</div>;
    }
}
export default Cmp

? getSnapshotBeforeUpdate(prevProps, prevState)
在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息,此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
? componentDidUpdate(prevProps, prevState, snapshot)
会在数据更新后会被立即调用。首次渲染不会执行此方法。
? componentWillUnmount()
会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作
? componentWillMount() / UNSAFE_componentWillMount()
已被弃用,在挂载之前被调用。它在 render() 之前调用,因此在此方法中同步调用 setState() 不会触发额外渲染
? componentWillReceiveProps()/UNSAFE_componentWillReceiveProps(nextProps)
已被弃用,它会在已挂载的组件接收新的 props 之前被调用。
? componentWillUpdate()/UNSAFE_componentWillUpdate(nextProps, nextState)
已被弃用,当组件收到新的 props 或 state 时,会在渲染之前调用此方法,初始渲染不会调用此方法。

十、组件传值

10.1、父组件与子组件传值
? 父组件将自己的状态传递给子组件,子组件当做属性来接收,当父组件更改自己状态的时候,子组件接收到的属性就会发生改变 props
? 父组件利用ref对子组件做标记,通过调用子组件的方法以更改子组件的状态,也可以调用子组件的方法(说明:ref获取当前组件对象,只针对的是使用类创建的组件才可以用此方案,类有实例,而函数没有实例)

注:ref只能给类组件去使用,得到的是组件实例,而函数组件没有实例的

在这里插入图片描述
ref的另类写法
在这里插入图片描述

10.2、子组件传值给父组件
父组件将自己的某个方法传递给子组件,在方法里可以做任意操作,比如可以更改状态,子组件通过this.props接收到父组件的方法后调用,来给父组件传值。

因为在react中props是可以传任意类型数据,而函数是不是也是任意类型数据之一

父组件  props传过来当前自身的函数体  子组件通过props得到  this.props.名称()执行

在这里插入图片描述

10.3、跨组件通信
在react没有类似vue中的事件总线来解决这个问题。在实际的项目中,当需要组件间跨级访问信息时,如果还使用组件层层传递props,此时代码显得不那么优雅,甚至有些冗余。在react中,我们还可以使用context来实现跨级父子组件间的通信。
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。
在React的Context中,数据我们可当成商品,发布数据的组件会用provider身份(卖方),接收数据的组件使用consumer身份(卖方)
在这里插入图片描述

? 创建Context对象
当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值。

// 定义全局context,全局数据源 
import { createContext } from "react"
export default createContext()

在这里插入图片描述

? 发布消息
在App.jsx组件中发布消息,这样所有的组件都可以消费它的消息。

import context from "./Context ";
let { Provider } = context;

class App extends Component {
    state = {
        count: 12345,
    };

    render() {
        return (
            <div>
                <Provider value={this.state.count}>
                    <Cmp1></Cmp1>
                    <Cmp2></Cmp2>
                </Provider>
            </div>
        );
    }
}

export default App;

在这里插入图片描述

? 组件消费
在子组件中通过Api完成消费动作,从而实现消息通信。消费的方式有2种:
方式1:通过组件消费

import context from "../Context ";
let { Consumer } = context;

class Cmp1 extends Component {
    render() {
        return (
            <div>
                <Consumer>
                    {(value) => {
                        return <div>获取到的值是:{value}</div>;
                    }}
                </Consumer>
            </div>
        );
    }
}

export default Cmp1;

方式2:通过绑定静成属性来消费

import React, { Component } from "react";
import context from "../Context ";

class Cmp2 extends Component {
    static contextType = context;
    render() {
        return <div>{this.context}</div>;
    }
}

export default Cmp2;

十一、网络请求

11.1、axios

react中通过npm来安装axios扩展 npm i -S axios

11.2、发起请求
以请求接口地址https://api.i-lynn.cn/ip为例,请求完毕后将当前我们自己的IP地址显示在视图中。

import React, { Component } from "react";
// 引入axios
import axios from "axios";

class App extends Component {
    // 初始化状态
    state = {
        ipInfo: {},
    };

    render() {
        // 从数据中获取ip、country、area
        let { ip, country, area } = this.state.ipInfo;
        return (
            <div>
                当前的IP地址是:{ip} - {country} / {area}
            </div>
        );
    }

    // 类似于vue中的mounted
    async componentDidMount() {
        let ret = await axios.get("https://api.i-lynn.cn/ip");
        // console.log(ret.data);
        // 修改状态
        this.setState(() => {
            return {
                ipInfo: ret.data,
            };
        });
    }
}

export default App;

在这里插入图片描述
在这里插入图片描述
列表展示
在这里插入图片描述

11.3、react的反向代理
在配置在src/setupProxy.js文件,并通过npm安装http-proxy-middleware,代理中间件模块 npm i -S http-proxy-middleware
配置反向代理

const {createProxyMiddleware: proxy} = require('http-proxy-middleware')

module.exports = app => {
  app.use('/api', proxy({
    target: 'http://localhost',
    changeOrigin: true,
    pathRewrite: {
      '^/api': ''
    }
  }))
}

在这里插入图片描述

十二、高阶组件(HOC)

高阶函数:
https://blog.csdn.net/weixin_46797477/article/details/108810821

? 把函数当作参数传递给另一个函数
? 把函数作为另一个函数的返回结果

高阶组件(Higher-Order Components)就是一个函数,传给它一个组件,它返回一个新的组件。
高阶组件:就相当于手机壳,通过包装组件,增强组件功能。

实现步骤:
? 首先创建一个函数
? 指定函数参数,参数应该以大写字母开头
? 在函数内部创建一个类组件或函数组件,提供复用的状态逻辑代码,并返回
? 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件(可选,如有)
? 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面

作用:
? 进行权限控制
? 路由限制
? 访问统计
? 统一布局

12.1、传统函数方式调用

# 定义高阶组件
// 定义一个函数,在函数内部创建一个相应类组件
function HocComp(Cmp) {
  // 该组件提供复用状态逻辑
  class Hoc extends React.Component {
    state = {
      a: 0,
      b: 0
    }
    render() {
      // 把当前组件的状态通过属性传给参数组件
      return <Cmp {...this.state} />
    }
  }
  return Hoc
}

# 使用高阶组件
function Cmp(props) {
  return (
      <p>
        a的值:{props.a}
        <hr/>
        b的值:{props.b}
      </p>
  )
}
// 组件来进行包装
let Mycmp = HocComp(Cmp)

class App extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
        <div>
          高阶组件
          <Mycmp></Mycmp>
        </div>
    )
  }
}

定义和使用高阶组件
在这里插入图片描述

对于数据的处理

在这里插入图片描述

12.2、使用装饰器调用
装饰器是一种函数,写成 @函数名。它可以放在类和类方法的定义前面。react脚手架创建的项目默认是不支持装饰器,需要手动安装相关模块和添加配置文件。

? 安装相关模块
		npm i -D customize-cra react-app-rewired
		
? 修改package.json文件中scripts命令
	  "scripts": {
	    "start": "react-app-rewired start",
	    "build": "react-app-rewired build",
	    "test": "react-scripts test",
	    "eject": "react-scripts eject"
	  }

在这里插入图片描述

? 在项目根目录中添加config-overrides.js配置文件
此文件可以理解为就是webpack.config.js的扩展文件
		const path = require('path')
		const {addDecoratorsLegacy, override} = require('customize-cra')
		
		const customize = () => (config) => {
		  config.resolve.alias['@'] = path.join(__dirname, 'src')
		  return config
		}
		
		module.exports = override(
		    addDecoratorsLegacy(),
		    customize()
		)

在这里插入图片描述

12.3、memoization(计算属性-记忆组件)
连续两次相同传参,第二次会直接返回上次的结果,每次传参不一样,就直接调用函数返回新的结果,会丢失之前的记录,并不是完全记忆,可以在它的参数中传入state数据从而实现了类似Vue中的计算属性功能

# 安装
	npm i -S memoize-one

# 引入
	import memoizeOne from 'memoize-one'

# 使用
	getValue = memoizeOne((x,y) => x+y)

# 调用
	render(){
	let total = this.getValue(this.state.x, this.state.y)
	return <div>{total}</div>
	}

在这里插入图片描述

十三、css-in-js

13.1、介绍
CSS-in-JS是一种技术,而不是一个具体的库实现。简单来说CSS-in-JS就是将应用的CSS样式写在JavaScript文件里面,而不是独立为一些css,scss或less之类的文件,这样你就可以在CSS中使用一些属于JS的诸如模块声明,变量定义,函数调用和条件判断等语言特性来提供灵活的可扩展的样式定义。CSS-in-JS在React社区的热度是最高的,这是因为React本身不会管用户怎么去为组件定义样式的问题,而Vue有属于框架自己的一套定义样式的方案。
styled-components 应该是CSS-in-JS最热门的一个库,通过styled-components,你可以使用ES6的标签模板字符串语法,为需要styled的Component定义一系列CSS属性,当该组件的JS代码被解析执行的时候,styled-components会动态生成一个CSS选择器,并把对应的CSS样式通过style标签的形式插入到head标签里面。动态生成的CSS选择器会有一小段哈希值来保证全局唯一性来避免样式发生冲突。
13.2、安装
npm i -S styled-components
13.3、定义样式

# 样式js文件
import styled from 'styled-components'
const Title = styled.div`
  color:red;
  font-size:16px;
  h3{
    color:blue;
    font-size:20px;
  }
`
export {
  Title
}

# 显示
// 就像使用常规 React 组件一样使用 Title
import React, { Component } from 'react'
import { Title } from './Styles'
class App extends Component {
  render() {
    return (
      <div>
        <Title>
           我只是一个标题
           <h3>你好世界</h3>
        </Title>
      </div >
    );
  }
}
export default App

在这里插入图片描述

13.4、样式继承

# 样式
import styled from 'styled-components'

const Button = styled.button`
  font-size: 20px;
  border: 1px solid red;
  border-radius: 3px;
`;
// 一个继承 Button 的新组件, 重载了一部分样式
const Button2 = styled(Button)`
  color: blue;
  border-color: yellow;
`;
export {
  Button,
  Button2
}

# 显示
import React, { Component } from 'react'
import {
  Button,
  Button2
} from './Styles'
class App extends Component {
  render() {
    return (
      <div>
        <Button>我是一个按钮1</Button>
        <Button2>我是一个按钮2</Button2>
      </div >
    );
  }
}
export default App

在这里插入图片描述

13.5、属性传递

# 样式
import styled from 'styled-components'

const Input = styled.input`
  color: ${props => props.inputColor || "blue"};
  border-radius: 3px;
`;
export {
  Input
}

# 显示
import React, { Component } from 'react'
import {
  Input
} from './Styles'
class App extends Component {
  render() {
    return (
      <div>
        <Input defaultValue="你好" inputColor="red"></Input>
      </div >
    );
  }
}
export default App

在这里插入图片描述

十四、状态管理(redux)

14.1、简介
https://www.redux.org.cn/
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。
简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。如果你的项目组件的数量和层级也变得越来越多,越来越深,此时组件间的数据通信就变得异常的复杂和低效,为了解决这个问题,引入了状态管理(redux)从而很好的解决多组件之间的通信问题。
在这里插入图片描述

14.2、安装Redux

redux不是内嵌在react框架中,使用时需要手动去安装 npm i -S redux

14.3、三大原则

? 单一数据源
	整个应用的 state 被储存在一棵对象结构中,并且这个对象结构只存在于唯一一个 store 中
? State 是只读的
	redux中的state只读的不可以直接修改
? 使用纯函数(reducer)来执行修改state
	为了修改了state数据,redux定义了一个reducer函数来完成state数据的修改,reducer会接收先前的 state 和 action,并返回 新的 state

操作原理图:
在这里插入图片描述

①、store通过reducer创建了初始状态
②、component通过store.getState()获取到了store中保存的state挂载在了自己的状态上
③、用户产生了操作,调用了actions 的方法 ④、actions的方法被调用,创建了带有标示性信息的action(描述对象)
⑤、actions将action通过调用store.dispatch方法发送到了reducer中
⑥、reducer接收到action并根据标识信息判断之后返回了新的state
⑦、store的state被reducer更改为新state的时候,store.subscribe方法里的回调函数会执行,此时就可以通知component去重新获取state

14.4、使用redux
计数器案例:
? 创建统一的数据源
在src目录下新建store/index.js作为数据源声明文件

// 常规导入
import { createStore } from "redux"

// 创建默认的数据源(state)
const defaultState = {
    // 初始数据
    count: 0
}

// 负责处理数据
function reducers(state = defaultState, action) {
    // 返回新的state数据
    return state
}


// 创建仓库
const store = createStore(reducers)

// 导出
export default store

在这里插入图片描述

? 组件中获取/设置数据

import React, { Component } from "react"
// 导入仓库
import store from './store'

class App extends Component {
    constructor(props){
        super(props)
        // 获取初始数据
        this.state = store.getState()
        // 订阅数据(获取更新)
        store.subscribe(() => {
            this.setState(state => store.getState())
        });
    }
    render() {
        return (
            <div>
                { /* 渲染的内容 */ }
            </div>
        );
    }

    incr() {
        // 任务清单
        const actionCreator = {
            type: 'incr',
            payload: 1
        }
        // 派发任务清单
        store.dispatch(actionCreator);
    }

}

export default App;

在这里插入图片描述

? 安装redux-devtools
为了方便调试redux(可选安装),建议去谷歌商店安装redux-devtools
在这里插入图片描述

在使用的时候需要参考其说明页面,参考其使用说明给代码做对应的调整。

// 创建仓库
const store = createStore(
    reducer,
    window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

随后打开浏览器调试工具就可以看到类似如下界面:
在这里插入图片描述

14.5、代码模块化
现在我们已经能够很好的进行redux的数据管理,但是有一个缺点就是所有的代码都写在一个文件中,需要按照模块化开发的规则进行对代码拆分。

store
  |- reducer目录
  |- methods目录
  |- actions目录
  |- index.js入口文件

在这里插入图片描述

十五、react-redux

网址:https://react-redux.js.org/
React-Redux是Redux的官方针对React开发的扩展库,默认没有在React项目中安装,需要手动来安装。react-redux是依赖于redux,所以你必须安装redux
你可以理解为react-redux就是redux给我们提供一些高阶组件,能解决的问题是:使用它以后我们不需要在每个组件中再去手动订阅数据的更新了。

npm i -S react-redux redux

? 定义Provider
在程序主文件index.js文件中,定义Provider
此处类似于之前跨组件通信处的Provider一样,旨在让全局的组件共享store中的数据

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import store from './store'
import App from './App'
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

在这里插入图片描述

? 在子组件中使用react-redux

import { connect } from 'react-redux'

class Cmp extends Component {}

const mapStateToProps = state => ({
    state
});

const mapDispatchToProps = dispatch => ({})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Cmp)

在这里插入图片描述
在这里插入图片描述
在组件中去使用react-redux的方案
在这里插入图片描述

十六、redux中间件

16.1、介绍
redux默认支持同步操作,异步操作怎么办?Action发出以后,Reducer立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。怎么才能Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件。中间件有很多,这里使用一个 Redux 官方出品的 中间件库:redux-thunk
在这里插入图片描述

16.2、使用redux-thunk
它支持函数返回,本质还是在在内部调用dispatch返回一个固定值对象

npm i -S redux-thunk

在createStore实例store中使用

import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
    reducer,
    applyMiddleware(thunk)
)
export default store


//注:需要注意,加上了以上代码后,redux的调试工具就会失效并且会报错,如果还想使用redux的调试工具,则需要调整代码为
const composeEnhancers = (typeof window !== "undefined" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) || compose
const store = createStore(
    reducer,
    composeEnhancers(applyMiddleware(thunk))
);


# action
export const incrCount = () => dispatch => {
  setTimeout(() => {
    dispatch({
      type: 'add',
      payload: 1
    })
  }, 1000)
}

十七、Redux模块化

Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。

import { combineReducers } from 'redux'
import admin from './reducer/admin'
import web from './reducer/web'

const reducer = combineReducers({
  admin,
  web
})

在redux入口文件中引入combineReducers方法,进行对于多个reducer合并操作

注:reducer中的action.type名称不要有重复,如要有重复它会执行N次

在这里插入图片描述

十八、路由

18.1、介绍
现代的前端应用大多数是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力更小,所以更受欢迎。为了有效的使用单个页面来管理多页面的功能,前端路由应运而生。

? 前端路由功能:让用户从一个视图(组件)导航到另一个视图(组件)
? 前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
? 使用React路由简单来说,就是配置路径和组件

在这里插入图片描述

18.2、路由使用
https://reactrouter.com/
18.2.1、安装路由模块

路由模块不是react自带模块,需要安装第3方模块 npm i -S react-router-dom

18.2.2、相关组件

? Router组件:包裹整个应用,一个React应用只需要使用一次
   Router: HashRouter和BrowserRouter
       HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
       BrowserRouter:使用H5的history API实现(localhost3000/first)
       
? Link/NavLink组件:用于指定导航链接(a标签)
	最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
	
? Route组件:指定路由展示组件相关信息(组件渲染)
 path属性:路由规则,这里需要跟Link组件里面to属性的值一致
    component属性:展示的组件

各组件关系示意图

在这里插入图片描述

定义项目使用路由,在入口文件/src/index.js文件中定义路由模式
在这里插入图片描述

定义路由规则和匹配成功的渲染组件
在这里插入图片描述

在浏览器中输入后尝试匹配
在这里插入图片描述

18.3、声明式导航
使用Link或NavLink组件完成声明式导航的定义

Link/NavLink区别
	? Link组件不会根据路由的变化而添加或修改编译后html标签中的属性
	? NavLink会根据路由的变化而自动修改编译后html标签中的属性
		如果当前的路由规则和Navlink中的To所写的规则一致则添加class样式,
		默认名称为	active,可以通过activeClassName来修改匹配成功后样式名称。
import React, { Component } from 'react'
import { HashRouter as Router, Route, Link } from 'react-router-dom'
import Home from './pages/Home'
import News from './pages/News'

class App extends Component {
  render() {
    return (
      <Router>
        <h3>导航区域</h3>
        <hr />
        <ul>
          <li>
            <Link to="/home">首页</Link>
          </li>
          <li>
            <NavLink to="/news">新闻</NavLink>
          </li>
        </ul>
        <hr />
        <Route path="/home" component={Home} />
        <Route path="/news" component={News} />
      </Router>
    );
  }
}
export default App

在这里插入图片描述

18.4、编程式导航
react-router-dom中通过history对象中的push/replace/go等方法实现编程式导航功能。

this.props.history.push({
  pathname: "/home",
  search: "from=404",	// 表示传递查询字符串
  state: {		// 隐式传参,地址栏不体现
    username: "admin",
  },
});

注:在react路由中this.props要想得到路由中的对象,则默认必须要通过路由规则匹配渲染的组件才能有此对象 - 必须是直接渲染的组件

在这里插入图片描述

18.5、路由参数
路由参数:在Route定义渲染组件时给定动态绑定的参数。

React路由传参方式有三种:
	? 动态路由参数(param)
	  	以“/detail/:id”形式传递的数据
	  	在落地组件中通过this.props.match.params得到
	? 查询字符串(query)
	  	通过地址栏中的 ?key=value&key=value传递
	  	在落地组件中通过this.props.location.search得到
	? 隐式传参(state),通过地址栏是观察不到的
	  	通过路由对象中的state属性进行数据传递
	  	在落地组件中通过this.props.location.state得到

在这里插入图片描述

404页面定义
在这里插入图片描述

18.6、嵌套路由
在有一些功能中,往往请求地址的前缀是相同的,不同的只是后面一部份,此时就可以使用多级路由(路由嵌套)来实现此路由的定义实现。

例如,路由规则如下
	admin/index
	admin/user

它们路由前缀的admin是相同的,不同的只是后面一部份。
实现方式:
? 先需要定义个组件,用于负责匹配同一前缀的路由,将匹配到的路由指向到具体的模块

<Route path="/admin" component={Admin}></Route>

? 创建模块路由组件负责指定各个路由的去向

render() {
    // 获取前缀,供后续地址做路由拼接
    let prefix = this.props.match.path;
    return (
        <div>
            <h1>欢迎使用后台管理程序</h1>
            <Route path={`${prefix}/user`} component={User}></Route>
            <Route path={`${prefix}/goods`} component={Goods}></Route>
        </div>
    );
}

在这里插入图片描述

18.7、三种路由渲染方式
18.7.1、component (组件对象或函数)

<Route path="/home" component={Home} /> 
// 或 
<Route path="/home" component={(router)=><Home {…router} />} />

18.7.2、render (函数)

 <Route path="/home" render={router=><Home {…router} />} />

在这里插入图片描述

在这里插入图片描述

18.7.3、children (函数或组件)

// 全匹配
<Route path="/about" children={router =>{
	return <div>children渲染</div>
}} />// 精确匹配
<Route path="/about" children={<About />} />

在这里插入图片描述

18.8、withRouter高阶组件
作用:把不是通过路由直接渲染出来的组件,将react-router 的 history、location、match 三个对象传入props对象上
默认情况下必须是经过路由匹配渲染的组件才存在this.props,才拥有路由参数,才能使用编程式导航的写法,执行this.props.history.push(’/uri’)跳转到对应路由的页面,然而不是所有组件都直接与路由相连的,当这些组件需要路由参数时,使用withRouter就可以给此组件传入路由参数,此时就可以使用this.props

// 引入withRouter
import { withRouter} from 'react-router-dom'

// 执行一下withRouter
export default withRouter(Cmp)

在这里插入图片描述
18.9、自定义导航组件
为何需要自定义导航?
因为在项目中往往不是所有的声明式导航都是需要a标签完成,有时候可能需要别的标签,此时如果在需要的地方去写编程式导航就会有代码重复可能性,就在对于公共代码进行提取。

思路:
	? 定义一个普通组件可以是类组件也可以是函数式组件
	? 父组件能向子组件传值  props
	? 此高阶组件不管路由规则是否匹配都要有渲染  children

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

十九、过渡动画组件

https://reactcommunity.org/react-transition-group/css-transition
19.1、基础使用
在项目中可能会有一些动画效果展示或是页面切换效果,css动画的方式,比较局限,涉及到一些js动画的时候没法处理了。react-transition-group是react的第三方模块,借住这个模块可以更方便的实现更加复杂的动画效果
? 安装

npm i -S react-transition-group

? 使用

state = {
    show: true
}

//执行动画
  handleToggle = () => {
    this.setState({
      show: !this.state.show
    })
}

render(){
return (
	<CSSTransition
          in={this.state.show}   //控制动画是否入场,为true时,动画进场,为false时动画出场
          timeout={1000}         //动画执行时间
	 classNames: '样式前缀名称' 
          unmountOnExit  //元素退场时,自动把DOM也删除
          >
             <div>玩转React Transition</div>
	</CSSTransition>
	<button onClick={this.handleToggle}>Animation</button>
)
}

// 自定义样式
.wuchen-enter {
  opacity: 0;
}
.wuchen-enter-active {
  opacity: 1;
  transition: opacity 200ms;
}
.wuchen-exit {
  opacity: 1;
}
.wuchen-exit-active {
  opacity: 0;
  transition: opacity 200ms;
}

19.2、结合animate.css
animate.css动画库集成到react-transation-group动画模块中
网址:https://animate.style/
通过cdn地址下载动画库
在这里插入图片描述

放到项目中的src/assets目录中
集成到CssTransation组件中

<CSSTransition
          in={this.state.show}   //控制动画是否入场,为true时,动画进场,为false时动画出场
          timeout={1000}         //动画执行时间
	 //自定义的类名(定义动画效果,进场前,进场后直到结束,结束前,结束后)
          classNames={{
            enter: 'animate__animated',
            enterActive: 'animate__fadeIn',
            exit: 'animate__animated',
            exitActive: 'animate__fadeOut'
          }}     
          unmountOnExit  //元素退场时,自动把DOM也删除
          >
             <div>玩转React Transition</div>
</CSSTransition>

在这里插入图片描述

19.3、列表过渡

<TransitionGroup>
	<CSSTransition
          key={变量}  // 通过此key值来判断列表中的子节点需要被插入还是移除,然后触发动画
          timeout={1000}         //动画执行时间
	 //自定义的类名(定义动画效果,进场前,进场后直到结束,结束前,结束后)
          classNames={{
            enter: 'animate__animated',
            enterActive: 'animate__fadeIn',
            exit: 'animate__animated',
            exitActive: 'animate__fadeOut'
          }}     
          unmountOnExit  //元素退场时,自动把DOM也删除
          >
             <div>玩转React Transition</div>
	</CSSTransition>
	<CSSTransition
          key={变量}  // 通过此key值来判断列表中的子节点需要被插入还是移除,然后触发动画
          timeout={1000}         //动画执行时间
	 //自定义的类名(定义动画效果,进场前,进场后直到结束,结束前,结束后)
          classNames={{
            enter: 'animate__animated',
            enterActive: 'animate__fadeIn',
            exit: 'animate__animated',
            exitActive: 'animate__fadeOut'
          }}     
          unmountOnExit  //元素退场时,自动把DOM也删除
          >
             <div>玩转React Transition</div>
	</CSSTransition>
</TransitionGroup>

在这里插入图片描述

19.4、路由过渡
路由切换时添加动画效果

render() {
    return (
      <div>
        <li><Link to="/home">Home</Link></li>
        <li><Link to="/child">Child</Link></li>
        <TransitionGroup>
          <CSSTransition
            timeout={1000}
            classNames='fade'
            unmountOnExit
	   // 通过此key值来判断列表中的子节点需要被插入还是移除,然后触发动画
	   // location.key属性不是一定有,只有路由模式为history时才有
	   // hash可以用 uri
            key={this.props.location.key}
          >
            <Switch>
              <Route path="/home" component={Home} />
              <Route path="/child" component={Child} />
            </Switch>
          </CSSTransition>
        </TransitionGroup>
      </div>
    )
  }

在这里插入图片描述

19.5、利用高阶组件给组件添加动画
并不想让所有的路由都有动画效果,只是想对指定的页面有路由切换效果,可以利用高阶组件来完成。让组件有动画

# 定义高阶组件
import React, { Component } from 'react'
import { CSSTransition } from 'react-transition-group'
//import '../assets/animate.css'

const withAnimation = Cmp => {
  return class extends Component {
    render() {
      return (
        <CSSTransition
          in={this.props.match !== null}
          timeout={600}
          classNames={{
            enter: 'animate__animated',
            enterActive: 'animate__fadeIn',
            exit: 'animate__animated',
            exitActive: 'animate__fadeOut'
          }}
          unmountOnExit
        >
          <Cmp {...this.props} />
        </CSSTransition>
      )
    }
  }
}

export default withAnimation

# 使用
@withAnimation
class Page extends Component {
    render() {
      return <div>高阶组件完成路由切换动画效果</div>
    }
}

// 使用高阶组件定义路由动画组件是一定要用它的Route children来渲染组件
<Route path="/home" children={props => <Page {...props} />} />

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

二十、immutable.js

20.1、概述
官网:https://immutable-js.github.io/immutable-js/
https://cloud.tencent.com/developer/section/1489374
Immutable.js出自Facebook,是最流行的不可变数据结构的实现之一。它实现了完全的持久化数据结构,使用结构共享。所有的更新操作都会返回新的值,但是在内部结构是共享的,来减少内存占用(和垃圾回收的失效)。

持久化数据结构:这里说的持久化是用来描述一种数据结构,指一个数据,在被修改时,仍然能够保持修改前的状态,即不可变类型。

结构共享:Immutable使用先进的tries(字典树)技术实现结构共享来解决性能问题,当我们对一个Immutable对象进行操作的时候,ImmutableJS会只clone该节点以及它的祖先节点,其他保持不变,这样可以共享相同的部分,大大提高性能。

惰性操作:创建的对象时其实代码块没有被执行,只是被声明了,代码在获取或修改的时候才会实际被执行

20.2、使用immutable优缺点

? 优点
	降低mutable带来的复杂度
	节省内存
	历史追溯性
	拥抱函数式编程
? 缺点
	需要重新学习api
	资源包大小增加(源码5000行左右)
	容易与原生对象混淆:由于api与原生不同,混用的话容易出错
	第3方组件库不支持,使用时,还需要转换

20.3、安装

npm i -S immutable

20.4、常用Api

Map(): 原生object转Map对象

const map1 = Map({ a: 1, b: 2, c: 3})
const map2 = Map({ a: 1, b: 2, c: 3})

console.log(map1 === map2) // false
console.log(map1.equals(map2)) // true
map.toJS() // 把map转为js对象

--------------------------------------------------------
List(): 原生array转List对象

const list1 = List([1, 2]);
const list2 = list1.push(3, 4, 5);
// 获取值
console.log(list2.get(0));

const list1 = List([ 1, 2, 3 ]);
const list2 = List([ 4, 5, 6 ]);
const list3 = list2.concat(list1);
console.log(list3.toArray())

--------------------------------------------------------
fromJS(): 原生js转immutable对象
const imState = fromJS({
  name: 'lisi',
  users: ['aa', 'bb']
})

# 获取数据
imState.get('name')
console.log(imState.get('users').get(1))
console.log(imState.getIn(['users', 0]))

--------------------------------------------------------
toJS(): immutable对象转原生js   不推荐使用
const state = imState.toJS()
console.log(state);

--------------------------------------------------------
set/setIn/update/updateIn  修改数据
const newState = imState.set('name', 'zhangsan')
const newState = imState.setIn(['name'], 'zhangsan')
const newState = imState.update('count', value => value + 1)
const newState = imState.updateIn(['count'], value => value + 1)

20.5、redux中集成immutable.js
? 安装redux-immutable
redux中利用combineReducers来合并reducer并初始化state,redux自带的combineReducers只支持state是原生js形式的,所以需要使用redux-immutable提供的combineReducers来替换原来的方法。
`

npm i -S redux-immutable

? 使用
它的合并是支持immutable对象合并

import { combineReducers } from 'redux-immutable'

把所有的reducer数据转换为immutable对象

import {fromJS} from 'immutable'
const defaultState = fromJS({})

二十一、项目

一、 **菜谱大全

1.1、项目背景
当下回家吃饭健康饮食的理念正在兴起。据调查显示,有超过九成的都市白领及年轻人其实都倾向于在家里吃饭,尤其是有小孩的家庭意愿会更加强烈, 因为他们普遍都认为在家里吃饭的幸福感会更高; 随着经济的快速发展,人们的生活水平的逐渐提高,对饮食质量要求也越来越高,但都市快节奏的生活让上班族们吃饭的目标性更小,通常只是到了时间随机选 择食物塞饱肚子。该美食网站倡导一种全新的健康的生活方式,用户可以根据网站上提供的食谱了解不同菜系的风格、做法及搭配,除了可以查看各种食谱学习做饭, 还可以在线与其他用户一起交流和分享做菜的心得,通过美食来感受生活之美。
1.2、技术栈

使用react框架来完成本次项目的实现,使用技术有如下一些:
	nodejs
	react
	react-router-dom
	redux  react-redux  redux-thunk  immutable redux-immutable
	styled-components 
	antd-mobile 
	react-transition-group 
	axios
	http-proxy-middleware
	配置装饰器(costomize-cra react-app-rewired) 等等

1.3、开发环境

开发环境为:windows
开发工具:vscode/webstorm + jsx插件 + eslint
开发调试工具:chrome浏览器
开发运行环境:node环境
代码管理:git
上线环境:linux + nginx

1.4、项目效果展示
在这里插入图片描述

1.5、项目初始化
? 在本机磁盘中指定位置创建一下react项目,命令如下

npx create-react-app cookbook

? 创建好项目后,进入项目目录先安装常规要使用的三方包,命令如下

npm i -D customize-cra react-app-rewired http-proxy-middleware

npm i -S redux react-redux redux-thunk styled-components immutable redux-immutable react-router-dom react-transition-group axios

? 清理创建好的项目中不需要的文件及文件夹

* 删除public目录下的部份内容
* 删除src目录下的部份内容

? 修改public/index.html
? 在src目录下创建根组件App.jsx与项目入口文件index.js
? 配置装饰器支持

// 在当前项目根目录下面创建一个名称为config-overrides.js文件,对webpack进行配置
const {
    override,
    addDecoratorsLegacy,
    addWebpackAlias
} = require("customize-cra");
const path = require("path");

module.exports = override(
    addDecoratorsLegacy (),
    // 添加webpack别名
    addWebpackAlias({
        ["@"]: path.resolve("./src"),
    })
);

? 修改package.json中的脚本命令为如下

  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test",
    "eject": "react-scripts eject"
  }

? 配置反向代理

// 在src目录下创建一个名称为setupProxy.js文件,提前为后续接口设置反向代理
const { createProxyMiddleware: proxy } = require("http-proxy-middleware");
module.exports = app => {
    app.use(
        "/api",
        proxy({
            target: "http://localhost:9000",
            changeOrigin: true,
	   pathRewrite:{
	      "^/api": ""
	   }
        })
    )
}

二、首页开发

2.1、Ant Design Mobile组件库
在线文档:https://mobile.ant.design/docs/react/introduce-cn
antd-mobile是Ant Design的移动规范的 React实现,服务于蚂蚁金服及口碑无线业务。它支持多平台,组件丰富、能全面覆盖各类场景,UI 样式高度可配置,拓展性更强,轻松适应各类产品风格。
? 在使用前需要先对包进行安装

npm i -S antd-mobile

? 按需加载组件代码和样式的 babel 插件

// 按需加载  tree-shaking
npm i -D babel-plugin-import

//  config-overrides.js 用于修改默认配置
const { override, fixBabelImports } = require('customize-cra')

module.exports = override(
   fixBabelImports('import', {
     libraryName: 'antd-mobile',
     style: 'css',
   })
)

在这里插入图片描述

? 使用

import React, { Component } from "react";
// 引入`antd-mobile`的按钮组件
import { Button } from "antd-mobile";

class App extends Component {
    render() {
        return (
            <>
                <Button type="primary">我是一个常规按钮</Button>
            </>
        );
    }
}
export default App;

在这里插入图片描述

2.2、底部导航实现
参考地址:https://mobile.ant.design/components/tab-bar-cn/
底部导航可以使用antd-mobile中的tab-bar组件完成此功能展示。
在这里插入图片描述

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

2.3、顶部导航

height: .4rem;
line-height: .4rem;
background: #FF6C0C;
font-size: .18rem;
text-align: center;
color:#fff;

在这里插入图片描述

2.4、轮播显示
参考地址:https://mobile.ant.design/components/carousel-cn
该功能可以使用antd-mobile中的Carousel组件

实现步骤:
	 在首页组件中引入滑块组件
	 编写滑块组件实现对应效果

在这里插入图片描述

2.5、mock数据
mock数据(faker数据),即使用假数据来模拟后台的数据。
为什么要做假数据?因为后端开发接口并产出接口文档没有哪么快,此时就需要我们自己来模拟请求数据。模拟的数据字段、格式等,需要和后端工程师沟通。这样,我们可以先通过模拟的数据继续完成前端的工作任务,待后端工程师写好数据接口并提供了接口信息后,我们只需要修改请求地址,前后端就实现了无缝衔接。

?安装json-server帮助我们快速启动一个web服务

npm i -g json-server

// data.json
{
  "data": {
    "id": 1,
    "name": "aaa",
    "age": 20
  }
}

json-server data.json

? 配置mock
?? 创建src/mock/mock.js文件(文件名并非固定)

// commonJs模块化
// json-server配置文件
const swiper = require("./swiper.json");


// 导出

module.exports = () => ({
    swiper,
});

?? 创建mock的路由文件src/mock/route.json

{
    "/api/swiper": "/swiper"
}

? ? 在package.json文件中的scripts中配置启动命令

{
   scripts:{
       "mock": "json-server ./src/mock/mock.js -r ./src/mock/router.json --port=9090"
   }
}

? 启动web服务

npm run mock
在这里插入图片描述

2.6、网络请求封装
封装一下网络请求的方法集合
在这里插入图片描述

反向代理配置一下
在这里插入图片描述

请求配置的请求方法编写
在这里插入图片描述

2.7、搜索组件

export const SearchBox = styled.div`
  width: 90vw;
  height: .46rem;
  display: flex;
  border: 1px solid #ff6c0c;
  margin: .15rem auto;
  border-radius: 5px;
  box-shadow: 1px 1px 5px #ccc;
  justify-content: center;
  align-items: center;
  img{
    width: .2rem;
    height: .2rem;
  }
  span{
    color:#555;
    margin-left: .1rem;
  }
`

在这里插入图片描述

2.8、热门分类

参考地址:https://mobile.ant.design/components/grid-cn/

在antd-mobile组件库中查找到对应的ui组件,使用已存在的组件来简化静态的布局

export const HotCateBox = styled.div`
  background: #fff;
  .title{
    padding: .15rem;
    color:#949494;
  }
`
<Grid data={hotcate}
              columnNum={3}
              itemStyle={{ height: '.5rem' }}
              onClick={(row, index) => {
                console.log(index, this.props.history.push)
              }}
              renderItem={dataItem => (
                <div>{dataItem.title}</div>
              )}
            />

2.9、精品好菜
静态布局展示
?html

<div>
<h1>精品好菜</h1>
<div>
<dl>
<dt>
<img src="http://www.mobiletrain.org/images/index/new_logo.png" />
</dt>
<dd>
<h3>青椒香干</h3>
<p>1000浏览  2000收藏</p>
</dd>
</dl>
</div>
</div>

?css

div{
padding-left: .1rem;
> h1 {
height: .5rem;
line-height: .6rem;
padding-left: .15rem;
color: #666;
padding-left: 0;
}
> div {
display: flex;
flex-wrap: wrap;
> dl {
width: calc(50% - 0.1rem);
background: #fff;
margin-right: .1rem;
margin-bottom: .1rem;
dt {
img {
width: 100%;
}
}
dd {
display: flex; 
flex-direction: column;
align-items: center;
padding: .1rem;
h3 {
font-size: .16rem;
}
p {
font-size: .12rem;
color: #666;
}
}
}
}

2.9.1、图片懒加载

npm i -S react-lazyload
在这里插入图片描述
在这里插入图片描述

三、分类开发

3.1、分类顶部切换
创建需要的组件和样式
?html

<ul>
<li class="active">分类</li>
<li>食材</li>
<li class="slider right"></li>
</ul>

?css

div{
height:.44rem;
background:#ee742f;
display:flex;
align-items:center;
justify-content:center;
ul{
width:1.4rem;
height:.3rem;
display:flex;
position:relative;
border:1px solid #fff;
z-index:0;
border-radius:.15rem;
li{
flex:1;
line-height:.3rem;
text-align:center;
color:#fff;
&:last-child{
position:absolute;
width:50%;
height:.3rem;
background:#fff;
z-index:-1;
border-radius:.15rem;
transform:translate(0,0);
transition:all 0.4s ease-in;
&.right{
  transform:translate(100%,0);
}
}
&.active{
color:#ee742f;
}
}
}

3.2、列表展示
?html

<div>
<div>
<ul>
<li class="active"><span>分类</span></li>
</ul>
</div >
<div>
<ul>
<li>内容</li>
</ul>
</div>
</div>

?css

.div{
height:100%;
display:flex;
> div:first-child{
width:.9rem;
> ul{
height:100%;
overflow-y:scroll;
li{
height:.5rem;
text-align:center;
line-height:.5rem;
background:#f3f3f3;
&.active{
background:#fff;
span{
display:inline-block;
height:100%;
border-bottom:1px solid #ee742f;

}
}
}
}
}
>div:last-child{
flex:1;
background:#fff;
padding:.2rem .1rem;
>ul{
display:flex;
flex-wrap:wrap;
overflow-y:scroll;
height:100%;
align-content:flex-start;
li{
width:33.3333%;
text-align:center;
height:.5rem;
line-height:.5rem;
color:#666;
}
}
}

3.2、列表展示
?html

<div>
<div>
<ul>
<li class="active"><span>分类</span></li>
</ul>
</div >
<div>
<ul>
<li>内容</li>
</ul>
</div>
</div>

?css

.div{
height:100%;
display:flex;
> div:first-child{
width:.9rem;
> ul{
height:100%;
overflow-y:scroll;
li{
height:.5rem;
text-align:center;
line-height:.5rem;
background:#f3f3f3;
&.active{
background:#fff;
span{
display:inline-block;
height:100%;
border-bottom:1px solid #ee742f;

}
}
}
}
}
>div:last-child{
flex:1;
background:#fff;
padding:.2rem .1rem;
>ul{
display:flex;
flex-wrap:wrap;
overflow-y:scroll;
height:100%;
align-content:flex-start;
li{
width:33.3333%;
text-align:center;
height:.5rem;
line-height:.5rem;
color:#666;
}
}
}

上线
配置nginx
在这里插入图片描述

hosts
在这里插入图片描述

四、hook

4.1、简介
在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。React在v16.8 的版本中推出了 React Hooks 新特性,Hook是一套工具函数的集合,它增强了函数组件的功能,hook不等于函数组件,所有的hook函数都是以use开头。

使用 React Hooks 相比于从前的类组件有以下几点好处:
	? 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
	? 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render/Props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

4.2、使用hook限制

? hook只能用在函数组件中,class组件不行
?  普通函数不能使用hook
? 函数组件内部的函数也不行
? hook定义时要注意先后顺序
? hook函数一定要放在函数组件的第一层,别放在if/for中

4.3、常用hook函数
4.3.1、useState

保存组件状态,功能类似于类组件中的this.state状态管理

import React, { useState } from 'react'


// useState 参数值,为数组1的默认值
// 对于一些有一些需要初始计算的可以使用回调函数的方式来初始值
// let [count, setCount] = useState(() => 0)
// 固定值初始值
let [count, setCount] = useState(0)

// jsx
<button onClick={() => setCount(count + 1)}>+++</button>

4.3.2、useEffect
此hook可以模拟函数组件的生命周期,函数组件对于在一些生命周期中操作还是无能为力,所以 React提供了 useEffect 来帮助开发者处理函数组件,来帮助模拟完成一部份的开发中非常常用的生命周期方法。常被别的称为:副作用处理函数。此函数的操作是异步的。

// useEffect 相当类组件中的3个生命周期 componentDidMount componentDidUpdate componetWillUnMount
import React, { useState, useEffect } from 'react'

const App = () => {

  let [count, setCount] = useState(0)
  // 参数1:回调函数
  // 如果没有第2个参数,表示 componentDidMount componentDidUpdate
  // 如果第2个参数为空数组 表示 componentDidMount
  // 如果第2个参数不空数组,只关注所写变量事件更新和挂载 componentDidMount componentDidUpdate

  useEffect(() => {
    if (count > 10) {
      document.title = count
    }
// 返回回调函数中就是 componetWillUnMount
//  在执行下一个 effect 之前,上一个 effect 就已被清除
    return () => {
      // 在此处,就可以把setTimout setInterval  清空
    }
  }, [count]);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 1)}>+++</button>
    </div>
  );
}

4.3.3、useReducer
useReducer 这个 Hooks 在使用上几乎跟 Redux一模一样,唯一缺少的就是无法使用 redux 提供的中间件。

import React, { useState, useReducer } from 'react'

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

const App = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  return (
    <div>
      <h3>{state.count}</h3>
      <button onClick={() => dispatch({ type: 'increment' })}>+++</button>
      <button>---</button>
    </div>
  );

4.3.4、useContext
使用useContext可以方便我们获取Context中的数据源

import React, { useContext } from 'react'
import cxt from '../context/userContext'
const { Provider } = cxt

const App = () => {
  return (
    <div>
      <Provider value='hello'>
        <Cmp />
      </Provider>
    </div>
  )
}

function Cmp() {
  const context = useContext(cxt)
  return (
    <h1>
      {context}
    </h1>
  )
}

4.3.5、useMemo
记忆组件,可以理解为计算属性

import React, { useState, useMemo } from 'react';

const Memo = () => {
  const [count, setCount] = useState(1)
  const [val, setValue] = useState('')

  // 返回上一次缓存的值
  const sum = useMemo(() => {
    return count + 10
  }, [count]);

  return (
    <div>
      <h3>{count}-{val}-{sum}</h3>
      <div>
        <button onClick={() => setCount(count + 1)}>+ count</button>
        <input value={val} onChange={event => setValue(event.target.value)} />
      </div>
    </div>
  )
}

4.3.6、useCallback
记忆函数,它计算返回一个缓存函数。

import React, { useState, useCallback, useEffect } from 'react';

const App = () => {

  const [count, setCount] = useState(1);
  const [val, setVal] = useState('');

  const callback = useCallback(() => {
    return count
  }, [count])

  return (
    <div>
      <h1>父组件:{count}</h1>
      <Child callback={callback} />
      <div>
        <button onClick={() => setCount(count + 1)}>+count</button>
        <input value={val} onChange={event => setVal(event.target.value)} />
      </div>
    </div>
  )
}

function Child({ callback }) {
  const [count, setCount] = useState(() => callback())
  useEffect(() => {
    setCount(callback())
  }, [callback]);
  return (
    <div>
      <hr />
      <div>子组件:{count}</div>
    </div>
  )
}
4.3.7、useRef

useRef 跟 createRef 类似,都可以用来生成对 DOM 对象的引用

import React, { useRef } from 'react'
const Ref = () => {
  const username = useRef()
  const login = () => {
    console.log(username.current.value);
  }
  return (
    <div>
      <div>
        <input type="text" ref={username} />
        <br />
        <br />
        <button onClick={login}>添加用户</button>
      </div>
    </div>
  )
}

4.3.8、useImperativeHandle
使用它可以透传 Ref,因为函数组件没有实例,所以在默认自定义函数组件中不能使用ref属性,使用为了解决此问题,react提供了一个hook和一个高阶组件完帮助函数组件能够使用ref属性。

import React, { useRef, useEffect, useImperativeHandle, forwardRef } from "react"
function ChildInputComponent(props, useRef) {
  // const inputRef = useRef(null)
  //useImperativeHandle(ref, () => inputRef.current)
  // 穿透ref
  useImperativeHandle(ref, () => ({id:1,name:’aaa’}))
  return <input type="text" ref={ useRef } />
}
const ChildInput = forwardRef(ChildInputComponent)

function App() {
  const inputRef = useRef(null)
  const getValue = () => {
    console.log(inputRef.current.value)
  }

  return (
    <div>
      <ChildInput ref={inputRef} />
      <hr/>
      <button onClick={getValue}>获取一下数据</button>
    </div>
  )
}

4.3.9、useLayoutEffect
大部分情况下,使用 useEffect 就可以帮我们处理组件的副作用,但是如果想要同步调用一些副作用,比如对 DOM 的操作,就需要使用 useLayoutEffect,useLayoutEffect 中的副作用会在 DOM 更新之后同步执行。

import React, { useState, useLayoutEffect, useEffect } from 'react'

const Layout = () => {
  const [value, setValue] = useState(0)
  useLayoutEffect(() => {
    const title = document.querySelector("#title")
    console.log("useLayoutEffect");
    setValue(title.innerHTML);
  })

  useEffect(() => {
    console.log("useEffect")
  })

  return (
    <div>
      <div><h1 id="title">hello</h1><h2>{value}</h2></div>
    </div>
  )
}

4.3.10、react-redux-hook
react-redux支持了hook的解决方案,提供两个方法,很方便我们在项目去使用

import React from 'react'
import { useDispatch, useSelector } from 'react-redux'

const MyRedux = () => {
  const { username, password } = useSelector(state => state.userinfo)
  const dispatch = useDispatch()

  return (
    <div>
      <h3>{username}</h3>
      <hr />
      <button onClick={
        e => {
          dispatch({
            type: 'setname',
            name: 'aaaaa'
          })
        }
      }>修改一下姓名</button>
    </div>
  )
}

4.3.11、react-router-dom-hook
react-router-dom也提供了hook的解决方案

import {useHistory,useLocation,useParams} from 'react-router-dom'

4.4、自定义hook
定义自定义hook以use开头 + 函数名称,通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

import React, {useState, useEffect} from 'react'
// 是否在线
function useOnline() {
  const [online, setOnline] = useState(navigator.onLine)

  useEffect(() => {
    const handlerOnline = () => setOnline(true)
    const handlerOffline = () => setOnline(false)

    window.addEventListener('online', handlerOnline, false)
    window.addEventListener('offline', handlerOffline, false)

    return () => {
      window.removeEventListener('online', handlerOnline, false)
      window.removeEventListener('offline', handlerOffline, false)
    }
  })
  return online
}

function App() {
  const online = useOnline()
  return (
      <div>
        {
          online ?
              <h3 style={{color: 'green'}}>在线</h3>
              :
              <h3>离线了 </h3>
        }
      </div>
  )
}
  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-28 07:38:44  更:2021-07-28 07:40:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/8 12:31:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码