当前位置: 首页 > news >正文

React知识总结✨

React知识总结✨

🚀标志为在 vscode 书写的快捷提示

❗ 标志为易错点和注意点

✨✅表示重点

React基础

React概述

React 是一个用于构建用户界面的 JavaScript 库。React 主要用来写HTML页面,或构建Web应用

React 的特点

  • 1 声明式

    • 程序员只需写标签,React 负责渲染 UI,并在数据变化时更新 UI
  • 2 基于组件

  • 3 学习一次,随处使用

React 的基本使用

  • 安装 npm i react react-dom
  • 使用
    • 1 引入 react 和 react-dom 两个 js 文件
    • 2 创建 React(虚拟DOM) 元素
      • React.createElement('h1',null,'Hello React'(文本节点),React.createElement('span',null,'Hello React')(元素节点)
        • 返回值:React元素
        • 第一个参数:要创建的React元素名称
        • 第二个参数:该React元素的属性 eg:{ className:'box' }
        • 第三个及其以后的参数:该React元素的子节点
    • 3 渲染 React 元素到页面中
      • ReactDOM.render(el, document.getElementById('root'))
        • 第一个参数:要渲染的React元素
        • 第二个参数:DOM对象,用于指定渲染到页面中的位置(挂载点)

React 脚手架的使用

脚手架是开发 现代Web 应用的必备。

使用 React 脚手架初始化项目

  • 初始化项目,命令:npx create-react-app my-app

  • 启动项目,在项目根目录执行命令:npm start

npx命令

目的:提升包内提供的命令行工具的使用体验

现在:无需安装脚手架包,就可以直接使用这个包提供的命令

在脚手架中使用 React

1 导入 react 和 react-dom 两个包。

  • import xx from 'react'
  • import xx from 'react-dom'
    • ✨ React18 写法,多了 'react-dom/client'

2 调用 React.createElement() 方法创建 react 元素。❌

3 调用 ReactDOM.render() 方法渲染 react 元素到页面中。

  • ✨React18写法:ReactDOM.createRoot(document.getElementById('root')).render()
    • 渲染函数式组件,注意render 里面写成函数 如:render(App())

JSX

JSX 是 JavaScript XML 的简写,表示在 JavaScript 代码中写 XML(HTML) 格式的代码。

优势:声明式语法更加直观、与HTML结构相同,降低了学习成本、提升开发效率

语法糖=> 通过 babel 插件 => 转化成 JS 能识别的语法 React.createElement()

JSX 是 React 的核心内容。

使用步骤:

1 ⭐️推荐使用 JSX 语法创建 react(虚拟DOM) 元素

const title = <h1>Hello JSX</h1>

2 使用 ReactDOM.render() 方法渲染 react 元素到页面中

ReactDOM.render(title, root)

为什么脚手架中可以使用 JSX 语法?

  • JSX 不是标准的 ECMAScript 语法,它是 ECMAScript 的语法扩展。

  • reate-react-app 脚手架中已经默认有babel,无需手动配置。

JSX中对象无法直接显示,需转换成字符串

<h2> { JSON.stringfy(obj) } </h2>

String{false}

JSX 中使用 JavaScript 表达式

嵌入 JS 表达式
  • 数据存储在JS中

  • 语法:{ JavaScript表达式 }

  • 注意:语法中是单大括号,不是双大括号!

注意点
  • 单大括号中可以使用任意的 JavaScript 表达式 * + - / 三元表达式…
  • 能写 js 对象,一般只出现在 style 属性里
  • JSX 自身也是 JS 表达式 {JSX}
  • 能在{}中出现语句(比如:if/for 等)
JavaScript表达式(有值)
  • a
  • a+b
  • demo(1)
  • arr.map()
  • function test () {}
语句(代码)
  • if(){}
  • for(){}
  • switch(){case:xxxx}
✨JSX 插值表达式总结
  • 基本数据类型
    • string 、number 正常显示
    • ❗null 、undefined、boolean 不显示
  • 引用数据类型
    • 对象
      • 对象不能直接放在插值表达式中
    • 数组
      • 数组内的每项元素会当做一个个 dom 节点渲染出来
    • 函数
      • 函数本身也是对象,不能直接放在插值表达中,通常是要调用函数
      • 函数的返回值遵循上述规则
  • 其他
    • 三元、逻辑、🔔JSX本身:通常和其它的数据类型配合使用

JSX 的条件渲染

if/else 三元表达式 逻辑与运算符

JSX 的列表渲染

如果要渲染一组数组,应该使用数组的 map() 方法

注意:

  • 渲染列表时应该添加 key 属性,key 属性的值要保证唯一
  • 原则:map() 遍历谁,就给谁添加 key 属性
  • 尽量避免使用索引号作为 key

{songs.map(item => <li key={item.id}>{item.name}</li>)}

❗ 属性值写法:属性名={ 值 },与 Vue 不一样

JSX 的样式处理

行内样式 —— style

12px 可省略成 12

<h1 style={{ color: 'red', backgroundColor: 'skyblue',fontSize:30 }}></h1>

⭐️类名 —— className

<h1 className="title">JSX的样式处理</h1>

总结:

React 完全利用 JS 语言自身的能力来编写UI,而不是造轮子增强 HTML 功 能

❗️ 注意点

1、 属性名使用 驼峰命名法

2、 特殊属性名:class->className for->htmlFor tabindex->tabIndex

3、 没有子节点的React 元素可以使用 /> ,必须有结束标签

  • eg:<span/>

4、 推荐使用 ()包裹 JSX 避免 JS 中的自动插入分号陷阱。

  • const dv = <div> hello </div>

5、 标签中混入JS表达式时要用{}

6、 只有一个根标签,必须要有一个根节点:<></> <React.Fragment></React.Fragment>

7、 内联样式: 要用style={{ key:value }}的形式去写

8、 标签首字母

​ (1).若写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。

​ (2).若写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。

9、❗布尔类型、null、undefined 在 JSX 中不会显示在页面中(Vue 模板会转化成字符串再显示) String(true)

组件基础

使用React 就是使用组件,组件表示页面中的部分功能,组合多个组件实现完整的页面功能

特点:可复用、独立、可组合

React 组件的两种创建方式

1 使用函数创建组件

使用 JS 的函数(或箭头函数)创建的组件

  • 函数名称必须以大写字母开头
  • 函数组件必须有返回值,表示该组件的结构
    • 如果返回值为 null,表示不渲染任何内容
  • this 是undefined,因为babel编译后开启了严格模式
  • 没有 state 和 refs 属性
  • 可以使用 props
    • 函数传参的方式使用

渲染组件:

  • 用函数名作为组件标签名
  • 组件标签可以是单标签也可以是双标签
function Hello() {
    return (
    <div>这是我的第一个函数组件!</div>
    ) 
}
ReactDOM.render(<Hello />, root)

执行了ReactDOM.render(…之后,发生了什么?

1.React解析组件标签,找到了MyComponent组件。

2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
2 使用类创建组件

使用 ES6 的 class 创建的组件

  • 类名称也必须以写字母开头

  • 类组件应该继承 React.Component 父类,从而可以使用父类中提供的方法或属性

  • 类组件必须提供 render() 方法

  • render() 方法必须有返回值,表示该组件的结构

class Hello extends React.Component {
    render() {
        return <div>Hello Class Component!</div>
    } 
}
ReactDOM.render(<Hello />, root)
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。

//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。

执行了ReactDOM.render(…之后,发生了什么?

1.React解析组件标签,找到了MyComponent组件。

2.发现组件是使用类定义的,随后 new 出来该类的实例,并通过该实例调用到原型上的render方法。

3.将render返回的 虚拟DOM 转为真实DOM,随后呈现在页面中。
3 抽离为独立 JS 文件

组件作为一个独立的个体,一般都会放到一个单独的 JS 文件中

步骤:

创建

  1. 创建MyFooter.js

  2. 在 MyFooter.js 中导入React

  • import React from 'react'
  1. 创建组件(函数 或 类)

    // 类 src/componnets/MyFooter.js
    export default class MyFooter extends React.Component {
    render() {
    return <div>Hello Class Component!</div>
    } }
    
    // 函数 src/componnets/MyHeader.js
     export default () => {
      return (
        <header>
          <h1>页面头部文件</h1>
        </header>
      )
    }
    
  2. 在 MyFooter.js 中导出该组件

    • export default MyFooter

使用:

  1. 在 index.js 中导入 MyFooter组件

    • import MyFooterfrom './MyFooter'
  2. 渲染组件

    • ReactDOM.render(<Hello />, root)
// index.js

import React from 'react'
import ReactDOM from 'react-dom'
import MyFooter from './components/MyFooter'
import MyHeader from './components/MyHeader'

class App extends React.Component {
  render() {
    return (
      <>
        <MyHeader />
        <h1>Hello React</h1>
        <MyFooter />
      </>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

React 事件处理

事件绑定

语法:

on+事件名称={事件处理程序},比如:onClick={() => {}}/{handelClick} ❗注意不要写括号调用

React 事件采用驼峰命名法

事件对象

可以通过事件处理程序的参数获取到事件对象 event

React 中的事件对象叫做:合成事件(对象)

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

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

有状态组件和无状态组件

函数组件又叫做无状态(数据)组件,类组件又叫做有状态组件

函数组件

  • 没有自己的状态,只负责数据展示(静)

类组件

  • 有自己的状态,负责更新 UI,让页面“动” 起来

组件(类组件)中的 statesetState

state的基本使用

状态(state)即数据,是组件内部的私有数据,只能在组件内部使用

state 的值是对象,表示一个组件中可以有多个数据

  • 获取状态:this.state
  • ❗模板中使用 { this.state } 只有一个大括号

语法:

class Hello extends React.Component {
    // 简化语法
    state= {
        count: 0
    }
    render() {
        return (
<button onClick="this.setState({count:this.state.count + 1
})"></button>
	// 错误 this.state.count += 1
        )
	}
}

🔔 React 重要思想:单向数据流,this.state 只负责访问,更新需要用 this.setState()

不要直接修改 state 中的值,这是错误的!!!

setState()修改状态
  • 状态是可变的
  • 语法:this.setState({ 要修改的数据 })
  • ❌注意:要直接修改 state 中的值,这是错误的!!!
  • setState() 作用: 1. 修改 state 2. 更新UI (调用了 render 方法)
  • 思想:数据驱动视图

可以修改 state,后面使用 setState({})方法可以使render方法重新执行,进而更新数据

❌常见错误:通过赋值的方式修改 state,render 方法不会运行,视图不会更新(数据和视图不同步)

从 JSX 中抽离事件处理程序

原因:事件处理程序中 this 的值为 undefined

  • 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用。类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined

希望:this 指向组件实例(render方法中的 this 即为组件实例)

自定义方法————要用赋值语句的形式+箭头函数

事件绑定 this 指向

React 事件处理 语法:

on事件类型={ 事件处理函数 } ❗ 花括号内绑定的是事件处理函数,而不是函数的调用

eg:onClick={this.handleClick}

1 箭头函数

利用箭头函数自身不绑定this的特点

render() 方法中的 this 为组件实例,可以获取到 setState()

事件传参 语法:on事件类型={ () => this.handleXxx(参数) }

<button onClick={() => this.onIncrement(1)}></button>

2 Function.prototype.bind()

利用ES5中的bind方法,将事件处理程序中的this与组件实例绑定到一起

class Hello extends React.Component {
    constructor() {
    	super()
    	this.onIncrement = this.onIncrement.bind(this) 
        this.state = { count:0 }
    }
    // ...省略 onIncrement
    render() {
    	return (
    	<button onClick={this.onIncrement}></button>
    	)
	}
}
3 ⭐️class 的实例方法

利用箭头函数形式的class实例方法

注意:该语法是实验性语法,但是,由于 babel 的存在可以直接使用

onIncrement = () => {
	this.setState({})
}
onIncrement 放在 实例对象上

由于 onIncrement 是作为onClick的回调,所以不是通过实例调用的,是直接调用

//类中的方法默认开启了局部的严格模式,所以changeWeather中的this为undefined
补充注意点
  • React 组件自带的结构体,this 指向底层做过处理,如 render 结构指向为组件实例
  • ❗ 如果是自定义的函数,需要写成箭头函数写法,否则 this 指向为 undefined

表单处理 & ref

受控组件
  1. React 将 state 与表单元素值 value (相当于 v-model 双向绑定)绑定到一起,由 state 的值来控制表单元素的值
  2. React 对表单元素的 onChange 事件进行封装,统一使用 onChange 获取表单值即可

步骤:

1 在 state 中添加一个状态,作为表单元素的value值(控制表单元素值的来源)

  • state = { txt: '' }
  • <input type="text" value={this.state.txt}/>

2 给表单元素绑定 change 事件,将 表单元素的值 设置为 state 的值(控制表单元素值的变化)

  • <input type="text" value={this.state.txt} onChange={e => this.setState({ txt: e.target.value })} />

❗示例总结:

1 文本框、富文本框、下拉框 操作value属性

2 复选框 操作checked属性

handleChecked = e =>{
    this.setState({ isChecked: e.target.value })
}
state = { isChecked:'' }
<input type:'checkbox' checked={ this.state.isChecked } onChange={ this.handleChecked }/>

✨✨多表单元素优化

1 给表单元素添加name属性,名称与 state 相同

2 根据表单元素类型获取对应值

3 在 change 事件处理程序中通过对象名称的插值符号 [] 来修改对应的state

<input type="text" name="txt" value={this.state.txt} onChange={this.handleForm}/>
    
handelFrom = e =>{
     // 🎈利用解构语法简化代码
    const { name,type,checked,value } = e.target
    this.setDate({[name]:type === 'checkbox' ? checked : value})
}
非受控组件(DOM方式)❌

借助于 ref,使用原生 DOM 方式来获取表单元素值

场景:

  1. 获取 DOM 元素,和 document.querySelector() 获取无差别
  2. 获取组件,ref 价值所在✨

使用步骤

1 调用 React.createRef() 方法创建一个 ref 对象

constructor() {
	super()
	this.txtRef = React.createRef()
}

2 将创建好的 ref 对象添加到文本框中

<input type="text" ref={this.txtRef} />

3 通过 ref 对象获取到文本框的值

Console.log(this.txtRef.current.value)

组件进阶

组件通讯介绍

打破组件的独立封闭性,让其与外界沟通。这个过程就是组件通讯

组件的 props

作用:接收传递给组件的数据

1 传递数据:给组件标签添加属性

  • <Hello name="jack" age={19} />
    • 字符串 " "
    • 变量 {}

2 接收数据:函数组件通过参数props接收数据,组件通过 this.props 接收数据

class式:

class Hello extends React.Component {
    constructor(props) {
		// 推荐将props传递给父类构造函数
		super(props) 
    }
    render() {
        // 
        return (
         <div>接收到的数据:{ this.props.age }</div>
        )  // props 是一个对象
 	} 
}

函数式:

const Hello = props => {
	return (
	<div>接收到数据:{ props.name }</div>
	) 
}

const person = {name:'老刘',age:18,sex:'女'}
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))

特点:

  1. 可以给组件传递任意类型的数据
  2. props 是只读的对象,只能读取属性的值,无法修改对象
  3. 注意:使用组件时,如果写了构造函数,应该将 props 传递给 super(),否则,无法在构造函数中获取到 props!
    • 推荐将props传递给父类构造函数
    • 构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props

组件通讯的三种方式

1 父组件 -> 子组件

  1. 父组件提供要传递的state数据

    • state = { lastName: '王' }
  2. 给子组件标签添加属性,值为 state 中的数据

    • <Child name={ this.state.lastName } />
  3. 子组件中通过 props 接收父组件中传递的数据

    • 函数组件
      • function Child(props) { return <div>子组件接收到数据:{ props.name }</div> }
      • ✨函数组件的参数就是 props,可直接解构出父组件传过来的数据
    • 类组件
      • { this.props.name }
      • { ()=>this.props.HandleTilte(this.state.title) }

2 子组件 -> 父组件

利用回调函数,父组件提供回调,子组件调用,将要传递的数据作为回调函数的参数。

  1. 父组件提供一个回调函数(用于接收数据)

    • getChildMsg = data=> {this.setState({parentMsg:data})}
  2. 将该函数作为属性的值,传递给子组件

    • <Child getMsg={ this.getChildMsg } />
  3. 子组件通过 props 调用回调函数

    • handleClick=()=>{ this.props.getMsg(this.state.childMsg) }
  4. 将子组件的数据作为参数传递给回调函数

3 兄弟组件

将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态

公共父组件职责:1. 提供共享状态 2. 提供操作共享状态的方法

要通讯的子组件只需通过 props 接收状态或操作状态的方法

❗注意点:

  1. 可以给组件传递任意类型的数据
    • 包括虚拟DOM结构、函数(用来子向父传递数据)(比 Vue 更灵活)
  2. props 是只读的,不允许修改 props 的数据( Vue 的 props 也是只读的)

Context

App => Child 组件

  • 使用 Context,,实现跨组件传递数据(比如:主题、语言等)

使用步骤:

1 调用 React. createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件。

const { Provider, Consumer } = React.createContext()

2 使用 Provider 组件作为父节点

<Provider> <div className="App"><Child1 /></div> </Provider>

3 设置 value 属性,表示要传递的数据。

<Provider value="pink">

4 子组件调用 Consumer 组件接收数据。

<Consumer> { data => <span> data参数表示接收到的数据 -- {data}</span>}</Consumer>

5 孩子的孩子接收数据:

<Consumer> { data => <span> data参数表示接收到的数据 -- {data}</span>}</Consumer>

注:如果传过来的数据是对象,用解构,或者JSON.stringfy(date)

<Consumer>
  {({ a, b }) => {
    return (
      <div>
        {a}---{b}
      </div>
    )
  }}
</Consumer>

<Consumer>
  {value => {
    return <div>{JSON.stringify(value)}</div>
  }}
</Consumer>

❗✨注意事项:

  1. Consumer 内部嵌套的函数会自动执行,要注意函数的返回值需符合 模板数据展示语法要求

  2. 分拆的单文件组件如何处理?

    • 解决方案:确保 Provider 和 Consumer 是由同一个 Context 对象解构出来的
    // src/store.js
    import React from 'react'
    // 创建一个上下文对象,Provider 负责提供数据 ,Consumer 负责消费数据
    export const { Provider, Consumer } = React.createContext()
    
    // src/Test.js
    import { Consumer } from './store'
    // ...书写子组件的业务代码
    
    // src/index.js
    import { Provider } from './store'
    import Son from './Test'
    // 书写父组件的业务代码
    

props 深入

children 属性—组件的插槽

表示组件标签的子节点。当组件标签有子节点时,props 就会有该属性

值可以是任意值(文本、React元素、组件,甚至是函数)

React 插槽和 Vue 插槽写法区别:

Vue插槽 : <slot></slot>

React插槽: { this.props.children }

const Hello= props => {
	return (
        <div>
        组件的子节点:{props.children}
        </div>
	) 
}
<Hello>我是子节点</Hello>

这里的“我是子节点”是 props.children

子组件里使用:

{/* 渲染子节点 文本、组件、JSX节点 */}
{this.props.children}
{/* {this.props.children()} 子节点为函数时 */}

props 校验

允许在创建组件的时候,就指定 props 的类型、格式等,捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

使用步骤
  1. 导入 prop-types 包 (无需下载,React自带的) 🚀impt

  2. 使用组件名.propTypes = {colors: PropTypes.array} 来给组件的props添加校验规则 (写在组件定义之后)

  3. 也可用以下方法定义在组件内:(类组件✨)

    static propTypes = {
        title:PropTypes.string
    }
    
  4. 校验规则通过 PropTypes 对象来指定

约束规则
  1. 常见类型:array、bool、func、number、object、string

  2. React元素类型:element

  3. 必填项:isRequired

  4. ✨特定结构的对象:shape({ })

// 常见类型
potional: PropTypes.number,
// 必选
requiredFunc: PropTypes.func.isRequired,
// 特定结构的对象
optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
})

官网文档更多阅读

props 的默认值

场景:分页组件 -> 每页显示条数

作用:给 props 设置默认值,在未传入 props 时生效

App.defaultProps = { pageSize: 10 }

类组件✨

也可在组件内写:static defaultProps = { pageSize:10 }

传:<App pageSize={20}/>

函数组件✨

function List({ pagesize=10 }){return (...)}
知识补充:

类的静态成员:

class Person {
    // 类的实例属性
    name = 'zs'
   // 等价于 Person.age = 18 ,是添加给了 Person 类的成员,而非 new 出来的实例
    static age = 18
   // 原型实例方法
    sayHi() {
      console.log('哈哈')
    }
    static goodBye() {
      console.log('byebye')
    }
  }

请添加图片描述

组件的生命周期

组件的生命周期概述

意义:

  • 组件的生命周期有助于理解组件的运行方式、完成更复杂的组件功能、分析组件错误原因等

组件的生命周期:

  • 组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程,生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数

钩子函数的作用:为开发人员在不同阶段操作组件提供了时机。

只有 类组件 才有生命周期。

生命周期的三个阶段

请添加图片描述

1 创建时

执行时机 :组件创建时(页面加载时)

钩子函数的执行顺序: constructor -> render -> componentDidMount

钩子 函数触发时机作用
constructor创建组件时,最先执行,初始化的时候只执行一次1. 初始化state 2. 创建 Ref 3. 使用 bind 解决 this 指向问题等
render每次组件渲染都会触发渲染UI(注意: 不能在里面调用setState()
componentDidMount组件挂载(完成DOM渲染)后执行,初始化的时候执行一次1. 发送网络请求 2.DOM操作

✨开发经验:

  1. construcor()平常很少用,一般用简写的方式初始化 state ,即:

    • state = { msg:'Hello' }
  2. componentDidMount 🚀cdm 可快速生成结构

    • 组件挂载时可获取本地存储,如:

      componentDidMount(){
        this.setState({list:JSON.parse(localStorage.getItem('todos'))|| []})
      }
      
2 更新时

执行时机 : 1 setState() 2 forceUpdate() 3 组件接收到新的 props

钩子函数的执行顺序 : render -> componentDidUpdate🚀cdu

钩子函数触发时机作用
render每次组件渲染都会触发渲染UI(与 挂载阶段 是同一个render)
componentDidUpdate组件更新后(DOM渲染完毕)DOM操作,可以获取到更新后的DOM内容,不要直接调用setState

render生命周期注意点:

  1. setState 会导致递归更新!所以不能在 render 直接调用
  2. 不要在 render 内写定时器,会累加

✨React 可通过 componentDidUpdate 钩子实现类似 Vue 中的 watch 功能

componentDidUpdate(prevProps,prevState){
    // count 新值和旧值不一样,说明 count 变化了(Vue的watch)
    if(this.state.count !== prevState.count){
        console.log('count变化了,实现Vue的watch')
    }
}

也可在componentDidUpdate中设置本地存储

3 卸载时

执行时机 : 组件从页面中消失 🚀cwu

钩子函数触发时机作用
componentWillUnmount组件卸载(从页面中消失)执行清理工作(比如:清理定时器等)

render-props高阶组件

React组件复用概述

复用什么?
  1. state 2. 操作state的方法 (组件状态逻辑 )
两种方式:

​ 1. render props模式 2. 高阶组件(HOC)

注意:这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式(写法)

render props 模式

使用步骤

1 创建Mouse组件,在组件中提供复用的状态逻辑代码(1. 状态 2. 操作状态的方法

2 将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部

render() { return this.props.render(this.state) }

3 使用 props.render() 的返回值作为要渲染的内容

<Mouse render={(mouse) => <p>鼠标当前位置 {mouse.x},{mouse.y}</p>}/>

上面的 render 属性可以使用任意名

函数要是没有返回值,就是直接调用,然后返回了一个 undefined

children代替render属性

1 组件内部:

render(){ return this.props.children(this.state)}

2 使用组件:

<Mouse> <p>{({x,y} => <p>鼠标的位置:{x} {y})}</p> </Mouse>

代码优化

1 给 render porps 添加 props 校验

2 在组件卸载时解除 mousemove 事件绑定

Mouse.propTypes = { children: PropTypes.func.isRequired }

componentWillUnmount(){
   window.removeEventListener('mousemove',this.handleMouseMove)
}

高阶函数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。

2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有:Promise、setTimeout、arr.map()等等

可以实现函数的复用

//保存表单数据到状态中
    saveFormData = (dataType)=>{
        return (event)=>{
            this.setState({[dataType]:event.target.value})
        }
    }

用户名:<input onChange={this.saveFormData('username')} type="text" name="username"/>
						密码:<input onChange={this.saveFormData('password')} type="password" name="password"/>

不用函数柯里化的方法

saveFormData = (dataType,event)=>{
    this.setState({[dataType]:event.target.value})
}
<input onChange={ e => this.saveFormData('username',e)}
<input onChange={ e => this.saveFormData('password',e)}

函数的柯里化

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

  function sum(a){
    return(b)=>{
    return (c)=>{
    return a+b+c
         }
     }
  }

高阶组件

高阶组件(HOC,Higher-Order Component)是一个函数,接收要包装的组件,返回增强后的组件

高阶组件内部创建一个组件,在这个类组件中提供复用的状态逻辑代码,通过props将复用的状态传递给被包装组件 WrappedComponent

使用步骤:

1 创建一个函数,约定以 with 开头

2 指定函数参数,参数应该以大写字母开头(作为要渲染的组件)

3 在函数内部创建一个类组件,提供复用状态逻辑代码,并返回

4 在该组件中 渲染参数组件,同时将状态通过 props 传递给参数组件

function withMouse(WrappedComponent) {
    class Mouse extends React.Component {
        render(){
return <WrappedComponent {...this.state}  {...this.props}/>
        }
    }
    return Mouse
}

5 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面中

// 创建组件
const MousePosition = withMouse(Position)
// 渲染组件
<MousePosition />

6 设置displayName

  • 使用高阶组件存在的问题:

    • 得到的两个组件名称相同
  • 原因:

    • 默认情况下,React使用组件名称作为 displayName
  • displayName的作用:

    • 用于设置调试信息(React Developer Tools信息)
  • 设置方式:

    Mouse.displayName = `WithMouse${getDisplayName(WrappedComponent)}`
    
    function getDisplayName(WrappedComponent) {
    return WrappedComponent.displayName || WrappedComponent.name || 'Component' }
    

7 传递props

问题:

  • props丢失

原因:

  • 高阶组件没有往下传递props

解决方式:

<WrappedComponent {...this.state} {...this.props} />

React原理

✨setState() 的说明 — 🔔面试题-setState深入

更新数据

setState() 是异步更新数据的

❗后面的 setState() 不要依赖于前面的 setState()

对象写法:覆盖运行,多次调用 setState ,只会触发最后一次
count:1 

this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 2 })

{/* 🔔 视图结果为:3 */}
<h2>count:{this.state.count}</h2>
函数写法:所有的 count 更新会叠加运行
count:1

this.setState((state) => {
  return { count: state.count + 1 }
})

this.setState((state) => {
  return { count: state.count + 2 }
})

{/* 🔔 视图结果为:4 */}
<h2>count:{this.state.count}</h2>

推荐语法:

setState((state, props) => {})

参数state:表示最新的state
参数props:表示最新的props

第二个参数

DOM 更新后才触发的回调函数,相当于 Vue 的 this.$nextTick(()=>{ })

setState(updater[, callback])

this.setState(
    (state, props) => {},
    () => {
    document.title = '更新state后的标题:' + this.state.count
    }
)

知识点补充—事件的覆盖和叠加

 // 会覆盖前面的 onclick
  btn.onclick = function () {
    console.log('1')
  }
  btn.onclick = function () {
    console.log('2')
  }

// 不覆盖前面添加的 click 事件,全部叠加运行
  btn.addEventListener('click', () => {
    console.log(1)
  })
  btn.addEventListener('click', () => {
    console.log(2)
  })

JSX 语法的转化过程

请添加图片描述

JSX 仅仅是 createElement() 方法的语法糖(简化语法)
JSX 语法被 @babel/preset-react 插件编译为 createElement() 方法
React 元素:是一个对象,用来描述你希望在屏幕上看到的内容

请添加图片描述

组件更新机制

setState() 的两个作用:

  1. 修改 state
  2. 更新组件(UI)

过程:

父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件及其所有子组件)

官网原话:大部分情况下,你应该遵循默认行为。

✨组件性能优化

减轻 state

  1. 只存储跟组件相关的数据(比如:count/ 列表数据 /loading
  2. 不要做渲染的数据不要放在 state 中,比如定时器 id 等
    • 对于需要在多个方法中用到的数据,应该放在 this 中(这样不会加重 state)

      // 与 state 同级:
      timerId = -1
       componentDidMount() {
          this.timerId = setInterval(() => {
            console.log('定时器运行中')
          }, 1000)
        }
      
        componentWillUnmount() {
          clearInterval(this.timerId)
        }
      

避免不必要的重新渲染

由于 组件更新机制:

父组件更新会引起子组件也被更新,那么子组件没有任何变化时也会重新渲染

解决:使用钩子函数 shouldComponentUpdate(nextProps, nextState)🚀scu
  • 作用:通过返回值决定该组件是否重新渲染,返回 true 表示重新渲染,false 表示不重新渲染

  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行 (shouldComponentUpdate --> render)

class Hello extends Component{
    shouldComponentUpdate(nextProps, nextState){
        // 根据条件,决定是否重新渲染组件
        return boolean(eg: nextState.number !== this.state.number)
    }
    render(){...}
}

纯组件

PureComponent 与 React.Component 功能相似

✨区别:PureComponent 内部自动实现了 shouldComponentUpdate 钩子,不需要手动比较

PureComponent 底层源码揭秘:纯组件内部通过分别 对比 前后两次 propsstate 的值,如果没有改变,返回 false 以告知 React 可以跳过更新。

class Hello extends React.PureComponent{
    render(){
        return (
          <div>纯组件</div>
        )
    }
}
纯组件内部的对比是 shallow compare(浅层对比)
  • 对于值类型来说:比较两个值是否相同(直接赋值即可,没有坑)

    最新的state.number === 上一次的state.number // false,重新渲染组件
    
  • ❗对于引用类型来说:只比较对象的引用(地址)是否相同,而不是❌比较值

    const obj = { number: 0 }const newObj = obj(不要直接赋值)
    newObj.number = 2
    // ❌错误做法
    state.obj.number = 2
    最新的state.obj === 上一次的state.obj // true,不重新渲染组件
    
  • ❗state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据!

    ✅对象

    const newObj = { ...state.obj,number:2 }
    
    setState({ obj:newObj })
    

    ✅数组

    • ❌不要用数组的push / unshift 等直接修改当前数组的的方法
    • ✅应该用 concat 或 slice 等这些返回新数组的方法
    this.setState({
        list:[...this.state.list, {新数据}]
    })
    

虚拟 DOM 和 Diff 算法(实现部分更新)

虚拟 DOM本质是保存 DOM 关键信息的 JS 对象(state + JSX)

好处

提高DOM更新的性能,不频繁的操作真实Dom,在内存中找到变化的部分,再更新真实DOM

执行过程

  1. 初次渲染时,React 会根据初始state(Model),创建一个虚拟 DOM 对象(树)。
  2. 根据虚拟 DOM 生成真正的 DOM,渲染到页面中。
  3. 当数据变化后(setState()),重新根据新的数据,创建新的虚拟DOM对象(树)。
  4. 与上一次得到的虚拟 DOM 对象,使用 Diff 算法 对比(找不同),得到需要更新的内容。
  5. 最终,React 只将变化的内容更新(patch)到 DOM 中,重新渲染到页面。

React路由基础—V5

路由基本使用

使用React路由简单来说,就是配置 路径和组件(配对)

使用步骤:

  1. 安装:yarn add react-router-dom

  2. 导入路由的三个核心组件:Router / Route / Link

    import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
    
  3. ✨使用 Router 组件包裹整个应用(重要)

    • 一个 React应用只需用一次
    <Router>
        <div className="App">
        // … 省略页面内容
        </div>
    </Router>
    
  4. 使用 Link 组件作为导航菜单(路由入口)

    <Link to="/first">页面一</Link>
    
  5. 使用 Route 组件配置路由规则和要展示的组件(路由出口)

    const First = () => <p>页面一的页面内容</p>
    <Router>
        <div className="App">
            <Link to="/first">页面一</Link>
            <Route path="/first" component={First}></Route>
        </div>
    </Router>
    

常用组件

Router 组件:

包裹整个应用,一个 React 应用只需要使用一次

两种常用 Router:HashRouter 和 BrowserRouter

HashRouter:使用 URL 的哈希值实现(localhost:3000/#/first)

类似 Vue 的 new VueRouter()

✨**BrowserRouter**:使用 H5 的 history API 实现(localhost:3000/first)

✨✅ ❗开发经验:导入 HashRouter / BrowserRouter 组件后建议通过 as 改名为 Router

import { BrowserRouter as Router } from 'react-router-dom'
Link/NavLink 组件:

用于指定导航链接(a 标签),类似 Vue 的 <router-link> 组件

Route(OBJECT) 组件:

指定路由展示组件相关信息,相当于 Vue 的 <router-view> + 路由配置

  • path属性:路由规则

  • component属性:展示的组件

  • 推荐写法:通过 component 属性绑定组件,会通过 props 传递路由相关信息给子组件,工作常用

    <Route path='/find' component={Find} />

  • 以下写法只负责展示组件,没有传递路由相关 props 给子组件

    <Route path='/my'> <My/> </Route >

  • Route组件写在哪,渲染出来的组件就展示在哪

Switch 组件 — 404错误处理提示处理

通常,我们会把Route包裹在一个Switch组件中

Switch组件中,不管有多少个路由规则匹配到了,都只会渲染第一个匹配的组件

✅通过Switch组件内,最后一个 Route 组件可用于实现404错误页面的提示

<Switch>
    <Route path='/find' component={Find} />
    <Route path='/my' component={My} />
    {/* 处理 404 情况需要写 Switch 的最后 */}
    <Route component={NotFound}></Route>
</Switch>
❗注意事项:

Link/NavLink 组件需要嵌套到 HashRouter / BrowserRouter 组件内

NavLink 组件如果路径匹配的话,会自动添加 class="active" 的类名

  • 样式要引入 index.js 中

❗记得 import 再用

React路由 V5 的基本使用
import { BrowserRouter as Router, Route, Link, NavLink } from 'react-router-dom'
<Router>
  <h1>仿网易云音乐路由 - 公共的头部</h1>
  <Link to="/find">发现音乐</Link>
  <br />
  <NavLink to="/my">我的音乐</NavLink>
  <Route path="/find" component={Find}></Route>
  <Route path="/my" component={My}></Route>
</Router>

路由执行过程

  1. 点击 Link 组件(a标签),修改了浏览器地址栏中的 url 。
  2. React 路由监听到地址栏 url 的变化。
  3. React 路由内部遍历所有 Route 组件,使用路由规则( path )与 pathname 进行匹配。
  4. 当路由规则(path)能够匹配地址栏中的 pathname 时,就展示该 Route 组件的内容。

路由嵌套

src/views/Find.js

<NavLink to='/find/toplist'>排行榜</NavLink>

{/* React的 Route 组件:相当于 Vue 的 <router-view> + 路由配置 */}

<Route path='/find/toplist' component={Toplist} />

编程式导航

通过 JS 代码来实现页面跳转

  • history 是 React 路由提供的,用于获取浏览器历史记录的相关信息
  • push(path):跳转到某个页面,参数 path 表示要跳转的路径
  • go(n): 前进或后退到某个页面,参数 n 表示前进或后退页面数量(比如:-1 表示后退到上一页)
this.props.history.push('/my') 
// vue 的写法 this.$router.push('/my')

默认路由

表示进入页面时就会匹配的路由,默认路由path为:/

<Route path='/' component={Home} />

匹配模式

默认情况下,React 路由是模糊匹配模式

模糊匹配模式

模糊匹配规则:只要 pathname 以 path 开头就会匹配成功

  • path 代表 Route 组件的 path 属性
  • pathname 代表 Link 组件的 to 属性(也就是 location.pathname

精确匹配

避免默认路由在任何情况下都会展示

给 Route 组件添加 exact 属性,让其变为精确匹配模式

// 此时,该组件只能匹配 pathname=“/” 这一种情况
<Route exact path="/" component=... />

✨推荐:给默认路由添加 exact 属性。

动态路由和路由参数

动态路由参数,通过 /:xxx 占位

<Route path='/playlist/:id' component={PlayListDetail} />

获取动态路由参数

React: this.props.match.params.xxx Vue: this.$route.params.xx

React路由基础—V6

❗只写与 V5不同的点

核心组件

Routes

提供一个路由出口,满足条件的路由组件会渲染到组件内部,定义 path 和组件之间的关系

语法说明:
<Routes>
    <Route/>
    <Route/>
</Routes>    

Route

用于指定导航链接,完成路由匹配

<Route path="/about" element={ <About/> }

path 指定匹配的路径地址 element 指定要渲染的组件

编程式导航

语法:

navigage('/about,{ replace:true }')

  1. 导入useNavigate钩子函数

  2. 执行钩子函数得到跳转函数

  3. 执行跳转函数完成跳转

✨注:如果在跳转时不想加历史记录,可以添加额外参数 replace 为 true

跳转携带参数

1 searchParams传参

传参:navigage('/about/?id=100')

取参:

let [params] = useSearchParams()
let id = params.get('id')
2 params传参(path配合:/:id)

传参:navigage('/about/1001')

取参:

let [params] = useParams()
let id = params.get('id')

嵌套路由

1. App.js: 定义嵌套路由声明

<Routes>
    // 定义嵌套关系
    <Route path='/' element={ <Layout /> }>
        <Route path='/board' element={ <Board /> } />
    <Route/> 
</Routes>            

2. Layout.js: 使用 <Outlet/>指定二级路由出口

import { Outlet } from 'react-router-dom'

function Layout (){
    return (
        <div>
          // 二级路由出口
          <Outlet />
        </div>
    )
}

默认二级路由

场景:首先渲染完毕就要显示的二级路由

步骤:

  1. 给默认的路由标记 index
  2. 去掉跳转路径 path
<Routes>
    <Route path='/' element={ <Layout /> }>
        <Route index element={ <Board /> } />
        <Route path='/article' element={ <Board /> } />
    <Route/> 
</Routes> 

404路由配置

场景:当所有的路径都没有匹配成功的时候显示

语法说明:在各级路由的最后添加 *号路由 作为兜底

<Routes>
    <Route path='/' element={ <Layout /> }>
        <Route index element={ <Board /> } />
        <Route path='/article' element={ <Article /> } />
            <Route path='*' element={ <NotFound /> } />
    <Route/> 
</Routes> 

✨Hooks基础

什么是hooks

Hooks的本质:一套能够使 函数组件 更强大,更灵活的“钩子”,react v16.8开始

❗注意点:

  1. 有了hooks之后,为了兼容老版本,class类组件并没有被移除,俩者都可以使用
  2. 有了hooks之后,不能在把函数成为无状态组件了,因为hooks为函数组件提供了状态
  3. hooks只能在函数组件中使用

Hooks解决了什么问题

1 组件的状态逻辑复用

mixin混入的数据来源不清晰,HOC高阶组件的嵌套问题

2 class组件自身的问题

学习成本高,比如各种生命周期,this指向问题等

useState

替换之前的 state 写法

语法:

const [ 访问state的变量名, 设置state的函数名 ] = useState(初始值)

❗注意事项:

  1. useState 需要主动导入
  2. 函数组件没有 this ( this的值为 undefined )

state={ count:0 } ==> const [count,seCount] = useState(0)

this.setState({ count:this.state.count +1 })==> setCount(count + 1)

函数组件声明多个状态

useState() 可以调用多次,声明多个状态

为函数组件添加多个状态,再调用 useState 即可
const [isShow,setIsShow] = useState(true)

<button onClick={()=> setIsShow(!isShow)}></button>

useState 注意事项

1 只能出现在函数组件中
2 不能嵌套在if/for/其它函数中(react按照hooks的调用顺序识别每一个hook)
  • let num = 1
    function List(){
      num++
      if(num / 2 === 0){
         const [name, setName] = useState('cp') 
      }
      const [list,setList] = useState([])
    }
    // 俩个hook的顺序不是固定的,这是不可以的!!!
    
3 可以通过开发者工具查看hooks状态

请添加图片描述

useEffect - 副作用

主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用比如:

官方定义: 数据获取,设置订阅,localstorage操作以及手动更改 React 组件中的 DOM 都属于副作用。

官方提示:

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

1. 组件挂载时会执行一次 2. 每次组件更新时会执行 3. useEffect 内部返回的回调函数,会在组件被卸载时自动执行

基本使用

1 导入 useEffect 函数

2 调用 useEffect 函数,并传入回调函数

3 在回调函数中编写副作用处理(dom操作)

4 修改数据状态

5 检测副作用是否生效

useEffect - 传入和不传入第二个可选参数区别

没传第二个参数:componentDidMount + componentDidUpdate
  1. 组件挂载时会执行一次
  2. 每次组件更新(所有的state, props)时会执行
  3. useEffect 内部返回的回调函数,会在组件被卸载时自动执行
useEffect(()=>{
    console.log(11111, '没有传入第二个参数', count)
})
传递了第二个参数:componentDidUpdate + 内部判断参数变化

用法1:添加空数组

组件只在首次渲染时执行一次

// componentDidMount()
  useEffect(() => {
    console.log('组件挂载时,运行一次~~')
  }, [])

用法2:添加特定依赖项

类似 Vue 的立刻执行的 watch,在首次渲染时执行,在依赖项发生变化时重新执行

// componentDidUpdate()
  useEffect(() => {
    console.log('count更新时', count)
  }, [count])

✨Hooks进阶

useState - 回调函数的参数

使用场景

参数只会在初次渲染中起作用,初始 state 需要通过计算才能获得,则可以传入一个函数,返回计算后的初始值,供初始渲染的时候用

语法:

const [name,setName] = useState(()=>{
    // 编写计算逻辑    return '计算之后的初始值'
})

❗语法规则:

  1. 回调函数return出去的值将作为初始值
  2. 回调函数中的逻辑只会在组件初始化的时候执行一次

应用:

如何实现一个自增按钮,可以由使用的时候以传参的方式决定递增的初始值?

import { useState } from 'react'

export default function App() {
  return <Count initCount={10} />
}
function Count({ initCount }) {
  const [count, setCount] = useState(() => initCount)

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

useEffect清理操作

语法:

useEffect() 内部返回的回调函数,会在组件卸载时自动执行(相当于类组件的 componentWillUnmount)

作用:

执行清除操作(如:清理定时器,清理全局注册的事件,清理订阅等)

useEffect(() => {
    // 挂载时运行的代码
    console.log('组件挂载!!!')
    window.addEventListener('resize', fn)

    // ✅ 卸载时运行的代码
    return () => {
      console.log('组件卸载了~~~')
      window.removeEventListener('resize', fn)
    }
  }, [])

useEffect - 发送请求获取数据

❗开发注意事项:

❌1 不要给 useEffect 第一级函数添加 async

  • 因为异步会导致清理函数无法立即返回

❌常见错误写法: useEffect(async () => {}, [])

✅正确写法:

 const [channels, setChannels] = useState([])
useEffect(() => {
   async function loadDate(){
       // 使用 axios 请求数据
        const res = await request({
        url: '/v1_0/channels',
        method: 'get',
      })
      setChannels(res.data.channels)
   }
   loadDate()
 }, [])

2 ✅useEffect 回调函数中用到的数据就是依赖数据,就应该出现在依赖项数组中,如果不添加依赖项就会有bug出现

useRef

可以在函数组件中获取真实的dom元素对象或者是组件对象

使用步骤:

  1. 导入 useRef 函数
  2. 执行 useRef 函数并传入null,返回值为一个对象 内部有一个current属性存放拿到的dom对象(组件实例)
  3. 通过 ref 绑定 要获取的元素或者组件

获取dom

import { useEffect, useRef } from 'react'
function App() {  
    const h1Ref = useRef(null)  
    useEffect(() => {    
        console.log(h1Ref)  
    },[])  
    return (    
        <div>      
            <h1 ref={ h1Ref }>this is h1</h1>    
        </div>  
    )
}
export default App

获取组件实例

函数组件由于没有实例,不能使用ref获取,如果想获取组件实例,必须是类组件

Foo.js

class Foo extends React.Component {  
    sayHi = () => {    
        console.log('say hi')  
    }  
    render(){    
        return <div>Foo</div>  
    }
}
    
export default Foo

App.js

import { useEffect, useRef } from 'react'
import Foo from './Foo'
function App() {  
    const h1Foo = useRef(null)  
    useEffect(() => {    
        console.log(h1Foo)  
    }, [])  
    return (    
        <div> <Foo ref={ h1Foo } /></div>  
    )
}
export default App

useContext

实现步骤

  1. 使用createContext 创建Context对象
  2. 在顶层组件通过Provider 提供数据
  3. 在底层组件通过useContext函数获取数据

代码实现

import { createContext, useContext } from 'react'
// 创建Context对象
const Context = createContext()

function Foo() {  
    return <div>Foo <Bar/></div>
}

function Bar() {  
    // 底层组件通过useContext函数获取数据  
    const name = useContext(Context)  
    return <div>Bar {name}</div>
}

function App() {  
    return (    
        // 顶层组件通过Provider 提供数据    
        <Context.Provider value={'this is name'}>     
            <div><Foo/></div>    
        </Context.Provider>  
    )
}

export default App

杂记

TodoList案例重难点分析 — 类组件

❗不要直接改 state 的数据

如:id:list.length++ 这样其实就是直接改了 list ,给它直接加了一个 empty 值

控制显示隐藏:

style={{ display: list.length === 0 ? 'none' : '' }}

✨调用父组件的方法步骤:

  1. 父组件准备方法
  2. 传递给子组件
  3. 子组件解构出来,判断是否是带参函数
    • 是,() => addList(参数)
    • 不是,addList()

删除任务实现:

  1. 传 id

  2. 用数组过滤方法

  3. 更新 list 值

    delTodo = id => {
        this.setState({
          list: this.state.list.filter(item => item.id !== id)
        })
      }
    

✨❗更新 单选框状态

changeChecked = id => {
    const newList = this.state.list.map(item => {
      if (item.id === id) {
        // 需要更新 done
        return {
          ...item,
          done: !item.done
        }
      } else {
        return item
      }
    })
    this.setState({
      list: newList
    })
  }

✨更新全选状态

// 1. allChecked 由反选框计算出来
const allChecked = list.length !== 0 && list.every(item => item.done)

// 2. 改 allChecked 即更改反选框的值
changeAll = allChecked => {
    const newList = this.state.list.map(item => {
      return { ...item, done: allChecked }
    })
    this.setState({
      list: newList
    })
  }

✨添加任务用拓展运算符

addList = options => {
    this.setState({
      list: [...this.state.list, options]
    })
  }

计算完成任务总量的两种方法

1 filter
const count = list.filter(item => item.done).length
✅2 reduce 适合结合价格、数量等复杂的算法
const count = list.reduce((sum, item) => {
      if (item.done) return ++sum
      else return sum
    }, 0)

✨本地存储

组件挂载时获取本地存储:
componentDidMount() {
    this.setState({
      list: JSON.parse(localStorage.getItem('todos')) || [],
      type: JSON.parse(localStorage.getItem('todos-type')) || 'all'
    })
  }
组件(state,props)更新时,设置本地存储
  componentDidUpdate(prevProps, prevState) {
    localStorage.setItem('todos', JSON.stringify(this.state.list))
localStorage.setItem('todos-type', JSON.stringify(this.state.type))
  }
函数组件的存储:
// 组件挂载时 - 获取本地存储数据
  useEffect(() => {
    console.log('组件挂载时 - 获取本地存储数据')
    setList(JSON.parse(localStorage.getItem('todos')))
    setActive(localStorage.getItem('todos-type'))
  }, [])

  // 数据更新时 - 设置本地存储数据
  useEffect(() => {
    console.log('数据更新时 - 设置本地存储数据')
    localStorage.setItem('todos', JSON.stringify(list))
    localStorage.setItem('todos-type', active)
  }, [list, active])

❗类组件中自己写的函数记得要写成箭头函数!!

✅React 进阶

状态逻辑复用

高阶函数

什么是高阶组件?

高阶组件本质是一个函数,类比高阶组件,入参为组件,返回值也为组件,可以在函数体内给入参组件携带更多参数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。

1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数

2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。

常见的高阶函数有:PromisesetTimeoutarr.map()等等

高阶组件的代码实现

实现两个组件:1 在页面中显示鼠标在页面中的位置 2 在页面中实现span标签跟随鼠标移动

HocWrapper.js

import React from 'react'
const withMouse =(BaseComponent) => {
  class Wrapper extends React.Component {
        // 状态和逻辑公共部分
    state = {
        x: 0,
        y: 0,
    }
     moveHandler = (e) => {
      this.setState({
        x: e.pageX,
        y: e.pageY,
      })
    }
    }
    componentDidMount() {
    document.addEventListener('mousemove', this.moveHandler)
    }
    componentWillUnmount() {
    document.removeEventListener('mousemove', this.moveHandler)
    }
    render() {
      const { x, y } = this.state
      // 数据状态x,y 想怎么用怎么用
      return <BaseComponent x={x} y={y} />
    }
  }
	return Wrapper
}
export { withMouse }

使用高阶组件

import React from 'react'
import { withMouse } from './HocWrapper'

function AComponent({ x, y }) {
  return (
    <div>
      x:{x},y:{y}
    </div>
  )
}
// 调用高阶组件包裹源组件
const WrapperA = withMouse(AComponent)

class App extends React.Component {
  render() {
    return (
      <div>
        <WrapperA />
      </div>
    )
  }
}

export default App

render props

render props本质是一个组件,将组件的children属性设计为一个函数,在调用函数的同时传入额外参数

render props定义 renderProps.jsx

// render Props
import React from 'react'

class WithMouse extends React.Component {
  state = {
    x: 0,
    y: 0,
  }
  moveHandler = (e) => {
    this.setState({
      x: e.pageX,
      y: e.pageY,
    })
  }
  componentDidMount() {
    document.addEventListener('mousemove', this.moveHandler)
  }
  componentWillUnmount() {
    document.removeEventListener('mousemove', this.moveHandler)
  }
  render() {
    return <div>{this.props.children(this.state)}</div>
  }
}

export default WithMouse

使用 render props

import React from 'react'
import WithMouse from './renderProps'

function AComponent({ x, y }) {
  return (
    <div>
      x:{x},y:{y}
    </div>
  )
}

class App extends React.Component {
  render() {
    return (
      <div>
        <WithMouse>{(state) => <AComponent {...state} />}</WithMouse>
      </div>
    )
  }
}

export default App

hooks

天生为组合而生

hook实现

import { useState, useEffect } from 'react'

function useMouse () {
  const [p, setP] = useState({
    x: 0,
    y: 0
  })
  function moveHandler (e) {
    setP({
      x: e.pageX,
      y: e.pageY
    })
  }
  useEffect(() => {
    document.addEventListener('mousemove', moveHandler)
    return () => {
      document.removeEventListener('mousemove', moveHandler)
    }
  }, [])
  return [p.x, p.y]
}
export { useMouse }

hook使用

import { useMouse } from './withMouse'
function AComponent() {
  const [x, y] = useMouse()
  return (
    <div>
      x:{x},y:{y}
    </div>
  )
}

性能优化

优化的方向:减少没必要的组件更新

组件更新机制:

父组件更新会引起子组件也被更新,那么子组件没有任何变化时也会重新渲染

React.memo

React.memo是一个高阶组件,其实就是一个函数,通过比较**props的变化**决定是否重新渲染,如果通过内部的对比发现props并没有变化,则不会重新渲染,从而达到提高渲染性能的目的

基础使用

1)props不变不重新渲染

import React, { useState } from 'react'
// 通过React.memo包裹子组件
const Son = React.memo(() => {
  console.log('我是son组件,我更新了')
  return <div>this is son </div>
})

function App() {
  const [count, setCount] = useState(0)
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <Son />
    </div>
  )
}
export default App

再次运行发现,子组件只会在首次渲染时执行一次,后续App组件的更新不再影响它

2)props变化重新渲染

import React, { useState } from 'react'

const Son = React.memo(() => {
  console.log('我是son组件,我更新了')
  return <div>this is son </div>
})

function App() {
  const [count, setCount] = useState(0)
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <Son count={count} />
    </div>
  )
}

export default App

再次运行发现,子组件会随着count的变化而变化

复杂类型的Props

由于react内部对于复杂类型数据进行的是浅对比,只对比引用,如果引用不同,则代表俩次的props是不一致的,如果不一致,就重新渲染

import React, { useState } from 'react'

const Son = React.memo(() => {
  console.log('我是son组件,我更新了')
  return <div>this is son </div>
})

function App() {
  const [count, setCount] = useState(0)
  // 这里我们每次 修改count进行App组件更新 list都会生成一个不同的引用 所以造成子组件又重新渲染了
  const list = [1, 2, 3]
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <Son list={list} />
    </div>
  )
}

export default App

自定义props对比

如果不想使用React.memo内置的对比方式,而是想自己做判断,也是可以的,它为我们开放了对比函数,只需要在memo函数中传入第二个参数即可

import React, { useState } from 'react'

const Son = React.memo(
  () => {
    console.log('我是son组件,我更新了')
    return <div>this is son </div>
  },
  (prev, next) => {
    // 自定义对比关系 决定是否要重新渲染
    // 如果返回false 代表俩次props发生了改变 组件重新渲染
    // 如果返回true  代表来此props没有发生变化 组件不会重新渲染
    return prev.list.length === next.list.length
  }
)

function App() {
  const [count, setCount] = useState(0)
  const list = [1, 2, 3]
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      <Son list={list} />
    </div>
  )
}

export default App

useCallBack

作用:缓存一个函数,只在依赖项变化的时候才会更新引用值,经常与React.memo配合使用

未优化版本

我们上一节刚讲过,通过React.memo虽然可以在一定程度上避免子组件务必要的渲染,但是对于引用类型是无效的,那函数也是一个引用类型,且在父传子的时候也是可以把函数传给子组件的,这个时候就可以利用useCallBack和React.memo配合使用

import React, { useState } from 'react'

const Son = React.memo(() => {
  console.log('Son组件更新了')
  return <div>this is son</div>
})

function App() {
  const [count, setCount] = useState(0)
  const getName = () => {
    return 'this is app'
  }
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {/* 传给子组件一个函数引用 每次子组件都会跟着更新 */}
      <Son getName={getName} />
    </div>
  )
}

export default App

优化版本

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

const Son = React.memo(() => {
  console.log('Son组件更新了')
  return <div>this is son</div>
})

function App() {
  const [count, setCount] = useState(0)
  // 缓存
  const getName = useCallback(() => {
    return 'this is app'
  }, [])

  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {/* 传给子组件一个函数引用 每次子组件都会跟着更新 */}
      <Son getName={getName} />
    </div>
  )
}

export default App

useMemo

作用:缓存一个函数,该函数只在依赖项变化时才会重新执行此函数,在一些计算量很大的场景中非常有用

错误演示

import React, { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  const getNums = () => {
    // 模拟昂贵计算
    const nums = new Array(10000).fill(0).map((item) => item)
    console.log('计算了')
    return nums
  }
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {getNums()}
    </div>
  )
}

export default App

getNums内部有一个很大的计算,每次只有count发生变化,组件重新执行,计算也跟着重新执行,然后这个计算很明显只计算一次就可以了,这时候,就需要使用useMemo来进行优化

优化版本

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

function App() {
  const [count, setCount] = useState(0)
  const getNums = useMemo(() => {
    // 模拟昂贵计算
    const nums = new Array(10000).fill(0).map((item) => item)
    console.log('计算了')
    return nums
  }, [])
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {getNums}
    </div>
  )
}

export default App

✨虚拟DOM与diff算法

虚拟DOM是什么?

1 本质是Object类型的对象(一般对象)

2 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。

3 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

Diff算法

gth
}
)

function App() {
const [count, setCount] = useState(0)
const list = [1, 2, 3]
return (


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


)
}

export default App


## useCallBack

> 作用:缓存一个函数,只在依赖项变化的时候才会更新引用值,经常与React.memo配合使用

**未优化版本**

我们上一节刚讲过,通过React.memo虽然可以在一定程度上避免子组件务必要的渲染,但是对于引用类型是无效的,那函数也是一个引用类型,且在父传子的时候也是可以把函数传给子组件的,这个时候就可以利用useCallBack和React.memo配合使用

```jsx
import React, { useState } from 'react'

const Son = React.memo(() => {
  console.log('Son组件更新了')
  return <div>this is son</div>
})

function App() {
  const [count, setCount] = useState(0)
  const getName = () => {
    return 'this is app'
  }
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {/* 传给子组件一个函数引用 每次子组件都会跟着更新 */}
      <Son getName={getName} />
    </div>
  )
}

export default App

优化版本

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

const Son = React.memo(() => {
  console.log('Son组件更新了')
  return <div>this is son</div>
})

function App() {
  const [count, setCount] = useState(0)
  // 缓存
  const getName = useCallback(() => {
    return 'this is app'
  }, [])

  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {/* 传给子组件一个函数引用 每次子组件都会跟着更新 */}
      <Son getName={getName} />
    </div>
  )
}

export default App

useMemo

作用:缓存一个函数,该函数只在依赖项变化时才会重新执行此函数,在一些计算量很大的场景中非常有用

错误演示

import React, { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  const getNums = () => {
    // 模拟昂贵计算
    const nums = new Array(10000).fill(0).map((item) => item)
    console.log('计算了')
    return nums
  }
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {getNums()}
    </div>
  )
}

export default App

getNums内部有一个很大的计算,每次只有count发生变化,组件重新执行,计算也跟着重新执行,然后这个计算很明显只计算一次就可以了,这时候,就需要使用useMemo来进行优化

优化版本

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

function App() {
  const [count, setCount] = useState(0)
  const getNums = useMemo(() => {
    // 模拟昂贵计算
    const nums = new Array(10000).fill(0).map((item) => item)
    console.log('计算了')
    return nums
  }, [])
  return (
    <div className="App">
      <button onClick={() => setCount(count + 1)}>+{count}</button>
      {getNums}
    </div>
  )
}

export default App

✨虚拟DOM与diff算法

虚拟DOM是什么?

1 本质是Object类型的对象(一般对象)

2 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。

3 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。

Diff算法

对比 初始虚拟 DOM 树 和 更新后的虚拟 DOM 树,找到不同之处,最终,只将不同的地方更新到页面中

相关文章:

  • 一周时间深扒事务 总结代码演示篇 拿捏事务
  • 剑指offer79-87二进制枚举、回溯
  • 《Coding Monkey的自我修养》之MyBatis批量插入数据的三种方法
  • Windows应急响应信息采集工具
  • 舵机调试上位机
  • 瑞吉外卖 —— 3、员工管理
  • 走到上市前夕,叮当健康如何勾画“医药检险”蓝图?
  • 批量条件赋值、文本字段计算常用表达式
  • 计算机毕业论文Java项目源码下载学生宿舍管理系统|寝室管理
  • 分子动力学后处理自编程系列(2)------聚合物回转半径
  • 剑指offer57-61排序-堆
  • 数据⼀致性模型有哪些?
  • 【2023灵动股份笔试题】~ 题目及参考答案
  • 通过分箱对连续特征离散化,以提高线性模型的表现
  • 【Swift 60秒】04 - Doubles and booleans
  • k个最大的数及变种小结
  • mysql_config not found
  • Spark RDD学习: aggregate函数
  • Spring Cloud Feign的两种使用姿势
  • SpringBoot 实战 (三) | 配置文件详解
  • 个人博客开发系列:评论功能之GitHub账号OAuth授权
  • 如何设计一个微型分布式架构?
  • 世界上最简单的无等待算法(getAndIncrement)
  • 应用生命周期终极 DevOps 工具包
  • 用jquery写贪吃蛇
  • 扩展资源服务器解决oauth2 性能瓶颈
  • #git 撤消对文件的更改
  • #mysql 8.0 踩坑日记
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (4)STL算法之比较
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (附源码)小程序儿童艺术培训机构教育管理小程序 毕业设计 201740
  • (三分钟)速览传统边缘检测算子
  • (十八)三元表达式和列表解析
  • (十七)devops持续集成开发——使用jenkins流水线pipeline方式发布一个微服务项目
  • (推荐)叮当——中文语音对话机器人
  • *p=a是把a的值赋给p,p=a是把a的地址赋给p。
  • .Net 访问电子邮箱-LumiSoft.Net,好用
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘
  • [ C++ ] STL_stack(栈)queue(队列)使用及其重要接口模拟实现
  • [20171102]视图v$session中process字段含义
  • [Asp.net MVC]Asp.net MVC5系列——Razor语法
  • [C#]C# OpenVINO部署yolov8图像分类模型
  • [Django 0-1] Core.Handlers 模块
  • [Go WebSocket] 多房间的聊天室(三)自动清理无人房间
  • [HackMyVM]靶场 Quick3
  • [JavaWeb]—Spring入门
  • [Lucas定理]【学习笔记】
  • [Oh My C++ Diary]带参数的main()函数
  • [paddle]ModuleNotFoundError: No module named ‘paddle.nn.layer.layers
  • [pthon2.7+django1.2+sae]博客评论的异步提交
  • [SetContextPropertiesRule]{Context} Setting property 'source'
  • [Spring Boot 3] 整合NoSQL与构建RESTful服务