React快速上手

React个人笔记

1. 介绍

省略

2. 项目创建

  1. 前提条件:
    node>14,windows>7

  2. 创建项目三种方法

    1. npx create-react-app my-test
    2. npm init react-app my-test
    3. yarn create react-app my-test

    如果要使用ts,则npx create-react-app my-test --template typescript

  3. 项目目录与vue类似(省略)
    在src中是组件,并没有单独的文件夹。其中reportWebVitals.ts用于做性能测试,setupTests.ts做测试。在public文件夹下manifest.json文件用于其它端的app开发,类似于uniapp。

  4. react同样也是基于webpack的,但是当中没有类似于vue中的vue.config.js,如果我们要进行webapck的相关配置,就可以使用npm run eject将其分离出来,会出现一个config文件夹,里面就会有webpack.config.js以及一下其它的相关配置文件,此时就可以进行个性化定制。

    比如我们想使用@符号来表示src文件夹,那么就需要在webpack.config.js文件中找到alias(别名) 配置项进行相关配置

    alias:{
        '@':path.resolve('src')
    }
    

    如果使用的是ts,同时也要在tsconfig.js文件中启用@标识src文件夹

3. JSX语 法

我们可以直接声明一个类似HTML标签的变量赋值给JS变量,这个HTML标签类型的变量,就叫做JSX语法。jsx可以生成React元素,如果是ts文件则是tsx文件

注意事项

  1. 根标签包裹,可以使用div,也可以使用空标签(建议),JS变量如果是多行,建议外面包裹大括号

  2. 在JSX中写注释,注意是JS的注释,也就是js的注释写在大括号里面

  3. JSX中合法的JS表达式需要写在大括号里面,类似于vue中插值语法,不过vue中是两个大括号,在react中是一个大括号。

  4. 在JSX中因为class是类的关键字,因此如果想要在jsx中给一个标签添加类名,则要使用className而不能使用class。

  5. JSX表达是用来描述类似HTML + JS灵活操作的组合

    const Ele = (
    	<h1 className="red">你好,我是赵日天
        </h1>
    )
    // 本质上来讲并不是直接创建html,而是创建一个虚拟DOM
    const ele = React.createElement(
    	'h1',
        {
           className:'red',
         },
        children:'你好,我是赵日天'
    )
    

    React.createElement相关链接

条件判断以及循环

在vue中有v-for和v-if,但是在react中却不同。react中接近原生js,因此可以直接使用原生的js来实现

  • 循环,循环需要使用map表达式

    function App(){
        const arr = ['apple','orange','peach','watermalon']
    }
    return (
    <h1>水果列表</h1>
    arr.map(item=><div>item</div>)
    )
    
  • 直接写if进行条件判断,如果符合条件,则return对应的视图,否则返回空,简短的可以写三目运算符更为方便。

4.组件

react中组件定义方式有两种:函数组件类组件

函数组件

函数组件要使用大驼峰的命名方式来定义,可以使用箭头函数或者普通函数方式。

使用方法:

const MyApp = ()=>{
    const info = <h1>你好,我是函数组件</h1>
    return (
    		<>
              {info}
            </>
    		)
}

类组件

其中render函数是必不可少的,在render函数内部必须要返回一个JSX代码。

如果说当前返回的并没有涉及到标签,那建议将文件格式为js,直接返回函数即可。

render函数本质上来讲就是react的一个生命周期钩子函数

使用方法:

import React from 'react'

class App extends React.Component{

    // 必须要有render方法
    render(){
        const info = <div>你好,我是类组件</div>
        return (
            <>
        		<h1> 测试</h1>
            	{info}
            </>
    	)
    }
}
export default App

函数组件和类组件的区别

  1. 组件的定义方式不同
  2. 生命周期不同,函数组件没有生命周期
  3. 副作用操作执行不同:class通过生命周期函数,函数组件用hooks的useEffect
  4. state的定义、读取、修改方式不同,函数组件用hook的useState
  5. 类组件有this,函数组件没有
  6. 类组件有实例,函数组件没有
  7. ref使用不同,类组件可以获取子组件的实例,函数组件没有实例

官网推荐使用函数组件,使用Hooks编程

5.React State响应式数据对象

state是类组件的内置组件,用于类组件内部数据更新,也叫响应式对象。state就是组件描述某种现实情况的数据,由组件自己设置和更改,也就是说组件自己维护,使用state的目的就是为了在不同状态下控制组件显示,state是私有的,完全受控于当前组件。

注意:React无法直接将对象渲染到页面上,需要转化成字符串或者数组类型。

interface isState {
 msg:string,
 date:Date()
}
// Component中两个参数,第一个是props,第二个是state的类型
class App extends Component<any,isState> {
 constructor(props:any){
     super(props)
     this.state = {
         msg:'你好',
         date:new Date()
     }
 }
 render(){
     return(
     	<>
         	<h2>你好</h2>
         	<h3>{this.state.msg}</h3>
         	<h3>{this.state.date.toString()}</h3>
         </>
     )
 }
}

后面就可以直接不写constructor,直接写成

[public] state = {
    msg:'你好',
    date: new Date()
}

注意

  1. 修改响应式对象不能直接使用this.state进行修改,因为这样不会进行视图的刷新,需要使用this.setState()方法进行修改,这个方法是内置的,继承自React.Component,包括自增、自减等操作,因为这样也是直接进行修改的。

  2. state的更新是异步的,会采集每次更新修正,以采集最后一次为主,并通知页面更新。比如我们写了多个setState()来修改state中的变量,修改的方法前面的会被覆盖,只会以最后一次为准。如果想要多次的setState都生效,那么我们可以拷贝一个副本并返回。也就是如果我们直接操作对象,那么它们使用的是一个地址,会被影响,所以我们就是用回调函数,这样每次都会返回一个新的对象,不会互相影响。

    this.setState((state,props)=>{
        return {
            count:this.state.count + 1
        }
    })
    
  3. 对数组的修改需要注意,比如向数组中添加元素,push返回的是数组的长度,可以直接使用扩展运算符,返回一个新数组,或者也可以先给state进行push,之后再交给arr。

setState还支持第二个参数,第二个参数就类似于nextTick,也就是DOM更新完成了的阶段。

6.事件绑定

事件绑定采用小驼峰的命名方式

ES5的事件绑定

es5中没有箭头函数,大部分都是借助bind等来改变this的指向

在严格模式下,DOM上绑定事件,这个事件的回调函数中的this是undefined

class App extends Component<any,isState>{
    constructor(props:any){
        super(prop)
        this.state = {
            msg:'你好,世界',
            count:0
        }
        this.addCount = this.addCount.bind(this)
    }
    //累加行为
    addCount(){
        this.setState((state)=>{
            count:state.count + 1
        })
    }
	//累减行为
	subCount(){
        //如果不显式的传递参数,react会自动接受一个 e 作为事件对象(这个是被react封装过的)
        this.setState((state)=>{
            count:state.count - 1
        })
    }
    render(){
        return (
        	<>
            	<h1>this.state.msg</h1>
            	<h2>当前计数是:{this.state.count}</h2>
            	<button onClick={this.addCount}>点击增加</button>
            	<button onClick={this.subCount.bind(this)}>点击减少</button>
            </>
        )
    }
}

ES6语法的事件绑定

class App extends Component<any,isState> {
    state = {
    			msg:'你好,世界',
    			count:0
			}
	// ES6及其之后可以使用箭头函数,因为箭头函数没有自己的this,它会向上层去访问
	addCount=()=>{
        this.setState({
            count:this.state.count + 1
        })
    subCount(){
        this.setState({
            count:this.state.count - 1
        })
    }
    // render是React的生命周期钩子,会在初始化和state中变更时触发,类似于vue中的update
    render(){
            return (
            	<>
                <h1>当前的计数是:{this.state.count}</h1>
                <button onClick={this.addCount}>点击增加</button>
                {/*要切记,react中绑定事件必须是回调函数,千万不要在绑定事件时调用,否则会造成死循环*/}
                <button onClick={()=>{this.subCount()}}>点击减少</button>
                </>
            )
        }
    }
}

7.表单绑定

非受控组件:不需要绑定任何数据的组件叫做非受控组件,反之则是受控组件。

如果表单的某个属性,比如value与state中绑定,那么就叫做受控组件,而我们下面给绑定事件,再赋值,这个不叫受控组件。

class App extends Component<any,isState>{
    state = {
    firstName:''
}
chanHandler = (e:any)=>{
    this.setState({
        [e.target.value]:e.target.value
    })
}
render(){
    return(
    	<>
        <h2>{this.state.firstName}</h2>
        <input type="text" name="name" onChange={this.changeHandler} />
        </>
    )
}
}

不同的表单控件可能略有不同。

8.组件传值

父子组件传值

父子组件传值借助于props,但是子组件不能直接修改传递过来的props。在函数组件中,可以直接使用形参接收props,在类组件中,直接拿到props,进行调用即可 ,在工作中可能解构使用的较多{name}:{name:string}

const Son = (props:any)=>{
    return (
    	<>
        <h2>我是子组件,我接收到的值是{props.name}</h2>
        </>
    )
}
class Father entends Conponent<any,any>{
	render(){
        return(
        	<>
            	<Son name="zs"/>
            </>
        )
    }
}

设置默认值:如果说组件没有传递props,则可以设置默认值。对于函数组件,Son.defaultProps。对于类组件则直接static defaultProps = { }

属性验证:可以借助第三方插件库 prop-types,不过对于我们使用typescript来说,可以不用。

插槽

父向子传递时,可以在调用内部传递JSX内容。子组件则可以直接使用props.children来接收传递过来的数据。

在React中没有具名插槽,作为一个数组,按顺序获取即可

子传父

与vue中类似,调用父组件传递过来的自定义事件。子组件在调用父组件的自定义方法时,直接this.props.MyFn(arg)即可,在父组件中的函数参数则是子组件传递过来的值。

状态提升

在React中,将多个组件中需要共享的state向上移动到它们的最近公共父组件中,便可以实现共享state,这就是所谓的『整体提升』

将公共的属性放在父组件中,父组件将这个响应式数据分别通过props传递到子组件中,同时传递给子组件一个修改该数据的方法,之后子组件就可以进行操作。

上下文Context实现跨组件通信

当父级嵌套多层子级时,需要从祖先组件传递到孙组件甚至嵌套更多层时,我们使用props就需要多层传递,非常的繁琐。因此我们可以使用上下文Context,类似于vue中的ProvideInject,使用方法类似,并无太大差别。

本质上来讲,Context 是数据提供者和数据消费者的概念

// src/App.tsx
import React, { Component } from 'react';
interface isState { }
// 第一步:创建上下文对象,并设置默认值
const MyContext = React.createContext("默认值");

/*
第三步:
方案一,如果后代组件是类组件 可以通过 类的静态属性 static contextType = 上下文对象 获取祖先组件的值,
然后在jsx中 通过 this.context 取值即可,如果使用 TS 需要进行声明
方案二,如果不使用 静态属性,那么可以 通过 上下文对象.Consumer 来获取数据,
在其内部图片通过函数返回jsx代码,函数的默认参数就是祖先组件传递的值
*/
class Third extends Component<any, isState>{
// 方案一:直接拿到直接使用
static contextType = MyContext
// 在 TS 版本中使用需要注意
context!: React.ContextType<typeof MyContext>
render() {
  return (
      <div>
          <h3>third!!</h3>
          <i>{this.context}</i>
          <div>
              {/* 方案二:在需要消费数据的地方包裹内容,直接消费即可 */}
              <MyContext.Consumer >
                  {
                      (value) => {
                          return <mark>{value}</mark>
                      }
                  }
              </MyContext.Consumer>
          </div>
      </div>
  )
}
}

const Second = () => {
return (
  <div>
      <h2>second</h2>
      <Third />
  </div>
)
}
const First = () => {
return (
  <div>
      <h1>first</h1>
      <Second />
  </div>
)
}
class App extends Component<any, isState> {
render() {
  return (
      <div>
          {/* 第二步:在需要传值的地方,通过 上下文 对象 的 Provider 组件以及value属性配合传递数据 */}
          <MyContext.Provider value="传家宝">
              <First />
          </MyContext.Provider>
      </div>
  );
}
}

export default App

9.高阶组件

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。

HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。Higher - Order Components,其实就是一个函数,传给它一个组件,它返回一个新的组件。形如:

const NewComponent = HOC(YourComponent)

通俗的来讲,高阶组件就相当于手机壳,通过包装组件,增强组件功能。

代码示例:

import React, { Component } from 'react';
interface isState { }
localStorage.setItem('userName', '孙悟空')

// 1.高阶组件:就是一个纯函数,将一个组件作为参数,并且返回另一个新的组件
// 2.高阶组件内部可以实现 可以复用的业务逻辑
// 3.高阶组件内可以通过 类似于 父子组件关系 传递数据
// 4.<></>表示react的空标签,同理 react 常用的另一个空标签 React.Fragment
const withFooter = (OldCom: any) => {
 return class extends Component {
     state = { userName: '' }
     componentDidMount() {
         console.log(localStorage.getItem('userName'))
         this.setState({
             userName: localStorage.getItem('userName')
         })
     }
     render() {
         return (
             <React.Fragment>
                 <OldCom userName={this.state.userName} />
                 <footer>
                     前端 so Easy!
                 </footer>
             </React.Fragment>
         )
     }
 }
}

class Child1 extends Component<any, isState> {
 render() {
     return (
         <div>
             <h1>子组件 1 - {this.props.userName}</h1>
         </div>
     )
 }
}
const ChildA = withFooter(Child1)

class Child2 extends Component<any, isState> {
 render() {
     return (
         <div>
             <h1>child2-{this.props.userName}</h1>
         </div>
     )
 }
}
const ChildB = withFooter(Child2)

class App extends Component {
 render() {
     return (
         <div>
             <ChildA />
             <hr />
             <ChildB />
         </div>
     );
 }
}

export default App;

10.React中ref的使用

Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。

1、使用字符串 ref 获取 DOM

直接使用字符串方式获取 ref 节点已经被废弃,但是还能使用,不推荐使用

import React, { Component } from 'react';
interface isState { }

class App extends Component<any, isState> {
componentDidMount(): void {
  // 使用字符串的方式获取 ref DOM 节点,目前已经被废弃
  console.log(this.refs.title);
}
render() {
  return (
      <div>
          <h2 ref="title">我是一个标题</h2>
      </div>
  );
}
}

export default App;

2、使用 createRef 直接获取 DOM

import { Component, createRef } from 'react';
interface isState { }

class App extends Component<any, isState> {
// 使用 createRef 方式获取 ref DOM 节点,推荐玩法
titleRef = createRef<any>()
componentDidMount(): void {
  console.log(this.titleRef);
}
render() {
  return (
      <div>
          <h2 ref={this.titleRef}>我是一个标题</h2>
      </div>
  );
}
}

export default App;

3、使用 ref 获取当前组件并直接操作

import { Component, createRef} from 'react';
interface isState { }

class ChildA extends Component<any, isState> {
state = { count: 10 }
clickHandler = () => {
  this.setState({ count: this.state.count + 1 })
}
render() {
  return (
      <>
          <h1>childA - {this.state.count}</h1>
      </>
  )
}
}
// 1.在严格模式下,不要使用 字符串类型的ref,它已被废弃
// 2.如果在类组件中使用ref,建议使用 createRef
// 3.通过 createRef 创建的 ref 的 current值可以获取到子组件的实例(前提条件是子组件为类组件)
class App extends Component<any, isState>{
childARef = createRef<any>()
divRef = createRef<any>()
// 操作 child1,直接调用其组件内部的方法
add = () => {
  console.log(this.childARef.current.state)
  this.childARef.current.clickHandler()
}
// 在父组件中获取子组件实例并操作
componentDidMount() {
  console.log(this.childARef)
  console.log(this.divRef.current.innerHTML)
}
render() {
  return (
      <div>
          <button onClick={this.add}>加</button>
          <ChildA ref={this.childARef} />
          {/* ref也可以用在DOM */}
          <div ref={this.divRef}>ref-DOM</div>
      </div>
  );
}
}

export default App;

4、使用 ref 转发子组件

Ref 转发是一个可选特性,其允许某些组件接收 ref,并将其向下传递(换句话说,“转发”它)给子组件。

import { Component, createRef, forwardRef } from 'react';
interface isState { }

// 使用 forwardRef 高阶组件转发ref,需要接收第二个参数 ref 就代表当前转发的对象
const ChildB = forwardRef((props, ref: any) => {
return (
  <>
      <h1 ref={ref}>ChildB 组件</h1>
  </>
)
})

class ChildA extends Component<any, isState> {
state = { count: 10 }
clickHandler = () => {
  this.setState({ count: this.state.count + 1 })
}
render() {
  return (
      <>
          <h1>childA - {this.state.count}</h1>
      </>
  )
}
}
// 1.在严格模式下,不要使用 字符串类型的ref,它已被废弃
// 2.如果在类组件中使用ref,建议使用 createRef
// 3.通过 createRef 创建的 ref 的 current值可以获取到子组件的实例(前提条件是子组件为类组件)
class App extends Component<any, isState>{
childARef = createRef<any>()
childBRef = createRef<any>()
divRef = createRef<any>()
// 操作 child1,直接调用其组件内部的方法
add = () => {
  console.log(this.childARef.current.state)
  this.childARef.current.clickHandler()
}
// 在父组件中获取子组件实例并操作
componentDidMount() {
  // 如果子组件是函数式组件,调用子组件的时候,添加的ref属性 需要在 定义子组件时通过 React.forwardRef 高阶组件包裹,
  // 第二个参数就是传递的ref,这样就可以在父组件中获取到子组件中的 DOM 标签,获取组件内部的 DOM 标签玩法
  console.log(this.childARef)
  console.log(this.childBRef)
}
render() {
  return (
      <div>
          <button onClick={this.add}>加</button>
          <ChildA ref={this.childARef} />
          <ChildB ref={this.childBRef} />
          {/* ref也可以用在DOM */}
          <div ref={this.divRef}>ref-DOM</div>
      </div>
  );
}
}

export default App;

5、使用 ref 注意事项

  • ref 属性用于 HTML 元素时,使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。

  • ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。

  • 你不能在函数组件上使用 ref 属性,因为他们没有实例。如果非要使用,实际上是转发ref(父组件中获取到了子组件的某个DOM)

  • https://react.docschina.org/docs/hooks-reference.html#usedebugvalue)

11.Hooks

hooks只适用于函数组件,使用hooks则没有类组件

useState响应式对象

在类组件中使用state,在Hooks中则使用useState,会返回一个数组,可以使用数组解构。修改count的状态是一个异步依赖采集操作,也就是多个同样的会被覆盖。如果想要不被覆盖,可以使用函数柯里化的形式。

const [count,setCount] = useState(10)
// setCount是用来修改count的函数
const add = ()=>{
    setCount(count+1)
}

useEffect副作用

在hooks中,只能使用副作用来模拟不同的生命周期

在类组件中,我们使用componentDidMount生命周期来处理当组件挂载之后的逻辑。在Hooks中,则可以借助useEffect来模拟。需要注意的是,useEffect需要传递第二个参数才相当于componentDidUpdate,否则就相当于update。在第一个参数结束时,return一个函数对象,那么这个函数对象中就可以处理组件销毁前的逻辑,类似于componentWillUnmount

总的来说,useEffect可以模拟三个生命周期,componentDidMount和componentDidUpdate。如果只需要在页面初始化调用一次,则useEffect需要传递第二个参数,为空数组,如果监听更新事件,也就是监听所有,则不需要传递第二个参数。同样的,当在第一个参数结束时返回一个函数对象,则这个函数对象中就可以处理组件销毁前的逻辑。

useEffect生命周期的模拟

  1. 不传第一个参数,监听的是所有的响应式对象,类似于comonentDidUpdate
  2. 传第二个参数是空数组[],则只监听初始化渲染之后的操作,类似于componentDidMount
  3. 传第二个参数[postion],也就是之间听position的变化之后的操作,类似于watch
  4. 在副作用中,return一个函数对象,其中就可以写我们卸载页面之前的逻辑,类似于componentWillUnmount

useRef(或者Dom或组件)

使用useRef就可以获取当前的Dom元素或者是当前的组件

如果想要获取函数组件的ref,则必须要转发

// 函数式组件的 ref 转发,高阶组件思想
const Child = forwardRef((props, ref: any) => {
  return <div ref={ref}>child</div>
})

useReducer 管理数据

useReducer 也叫做状态管理,类似简易的 Vuex 来管理数据,使用步骤:

  1. 创建初始值 initialState
  2. 创建所有操作 reducer(state, action)
  3. 传给 userReducer,得到读和写API
  4. 调用写 ({type: '操作类型'})
  5. 总的来说,useReducer useState 的复杂版。
import { useEffect, useReducer } from "react"

// 1.创建初始化数据
const initalState = {
    count: 10,
    proList: []
}
// 2.设置reducer ----  唯一改变数据方式就是通过reducer实现
// action { type, payload }
const reducer = (state: any, action: any) => {
    switch (action.type) {
        case 'ADD':
            return Object.assign({}, state, { count: state.count + action.payload })
        case 'CHANGE_PRO_LIST':
            return Object.assign({}, state, { proList: action.payload })
        default:
            return state
    }
}

// 有了useReducer,不需要useState, 但是企业项目一般不去这么使用
// 因为,如果遇到多个组件需要共享状态时,单纯useReducer就显得无能为力
const App = () => {
    const [state, dispatch] = useReducer(reducer, initalState)

    useEffect(() => {
        fetch('http://121.89.205.189:3001/api/pro/list').then(res => res.json()).then(res => {
            dispatch({
                type: 'CHANGE_PRO_LIST',
                payload: res.data
            })
        })
    }, [])

    return (
        <div>
            {state.count} <button onClick={() => {
                dispatch({
                    type: 'ADD',
                    payload: 5
                })
            }}>加5</button>
            <ul>
                {
                    state.proList && state.proList.map((item: {
                        proid: any;
                        proname: any;
                    }) => (
                        <li key={item.proid}>{item.proname}</li>
                    ))
                }
            </ul>
        </div>
    );
};

export default App;

useContext 消费数据

  1. 使用 C = createContext(initial) 创建上下文

  2. 使用<C.Provider>圈定作用域

  3. 在作用域内使用 useContext(C)来使用上下文

import { createContext, useContext } from 'react';
// 1. 创建数据供应
const MyContext = createContext('MyContext')
const ColorContext = createContext('ColorContext')

const Child1 = () => {
    // 3. 消费数据
    const my = useContext(MyContext)
    const color = useContext(ColorContext)
    return (
        <div>
            {my} - {color}
        </div>
    )
}
const Child2 = () => {
    // 3. 消费数据
    const my = useContext(MyContext)
    const color = useContext(ColorContext)
    return (
        <div>
            {color} - {my}
        </div>
    )
}

const App = () => {
    return (
        <div>
            {/* 2. 供应数据 */}
            <MyContext.Provider value="传家宝">
                <ColorContext.Provider value="red">
                    <Child1 />
                    <Child2 />
                </ColorContext.Provider>
            </MyContext.Provider>
        </div>
    );
}

export default App;

useMemo(类似computed)

useMemo类似于vue中的computed,使用方式也很简单,可以对指定的响应式对象进行监听,要有返回值。useMemo和useEffect有差异,前者必须返回一个值,后者则是对数据变化的副作用进行监控。

import {useState} from 'react'
const App = ()=>{
const [countA,setCountA] = useState(0)
const [countB,setCountB] = useState(0)
//第二个参数数组中是需要依赖的响应式数据
const computeCount = useMemo(()=>{
  return countA + countB
},[countA,countB])
return (
	<>
   <div>counta 的值是:{counta}</div>
   <div>countb 的值是:{countb}</div>
   <Child value={cachedValue} />
   <button onClick={() => setCounta(v => v + 1)}>countA + 1</button>
   <button onClick={() => setCountb(v => v + 1)}>countB + 1</button>
  </>
)
}

memo(高阶函数)

只有依赖的props数据发生变化,才会触发子组件的更新,明显的提高了性能。

比如当我们在使用useMomo进行计算属性时,再将countA的值传递给子组件Child。如果Child是个普通组件,那么每次当countA或者countB的数据发生变化时,父组件就会重新进行diff比对,重新进行渲染,也就是再次调用子组件,我们在子组件中只接受了countA数据。我们只希望当countA的数据变化时才会再次更新Child子组件,此时就可以使用memo 。 memo是高阶函数的概念,也就是将我们的Child组件通过memo函数进行包裹,那么只有当其中依赖的数据发生变化时,Child组件才会再次进行渲染,明显的提高了性能。

import { useMemo, useState, memo } from "react";

interface IProps {
value: number
}
// 我们发现,不管是 counta、counta 任意发生变化都会触发子组件重新更新,明显消耗性能
// let Child = (props: IProps) => {
//     console.log("child 子组件被重新渲染了")
//     return (<div>父组件传递过来的值是:{props.value}</div>)
// }

// memo 是高阶组件概念,返回一个组件即为高阶组件
// memo的作用,只有依赖的数据发生变化时,才会触发子组件重新更新,明显提高性能
// 这里依赖的是 counta,而 countb 变化并不会触发子组件更新,如果都不依赖,则都不会更新
let Child = memo((props: IProps) => {
console.log("child 子组件被重新渲染了")
return (<div>父组件传递过来的值是:{props.value}</div>)
})

export default function Home() {
const [counta, setCounta] = useState(0);
const [countb, setCountb] = useState(0);
// useMemo 和 useEffect 的使用基本上一样,监听数据的变化,触发新的返回值
const cachedValue = useMemo(function () {
  return counta + 1
}, [counta])
return (
  <>
      <div>counta 的值是:{counta}</div>
      <div>countb 的值是:{countb}</div>
      <Child value={cachedValue} />
      <button onClick={() => setCounta(v => v + 1)}>countA + 1</button>
      <button onClick={() => setCountb(v => v + 1)}>countB + 1</button>
  </>
);
}

pureComponent

pureComponent就类似于函数组件中的memo使用。

手机品牌管理案例

import { useState, useMemo } from 'react'
import './MyPhone.css'

const MyPhone = () => {
  // 手机列表
  const [phoneList, setPhoneList] = useState([
    {
      name: '华为',
      date: new Date().toString(),
    },
    {
      name: '小米',
      date: new Date().toString(),
    },
    {
      name: '苹果',
      date: new Date().toString(),
    },
    {
      name: 'vivo',
      date: new Date().toString(),
    },
  ])
  // 弹窗显示
  const [modelShowTag, setModelShowTag] = useState(false)
  //添加手机名称输入
  const [inputPhone, setInputPhone] = useState('')
  // 查询手机输入
  const [searchInput, setSearchInput] = useState('')
  // 点击添加按钮显示弹窗
  const openModel = () => {
    setModelShowTag(true)
  }
  // 删除按钮
  const removePhone = (name: string) => {
    // alert('当前被删除的id' + name)
    setPhoneList(phoneList.filter((item) => item.name !== name))
  }
  // 点击弹窗中的取消按钮
  const closeModel = () => {
    setModelShowTag(false)
  }
  // 回车事件
  const enter = (e: any) => {
    // console.log(e)
    if (e.code === 'Enter') {
      setPhoneList([
        {
          name: inputPhone,
          date: new Date().toString(),
        },
        ...phoneList,
      ])
      setInputPhone('')
      setModelShowTag(false)
    }
  }
  // 点击弹窗中的确认按钮
  const addPhone = () => {
    // 输入的内容不能为空
    if (inputPhone === '') {
      alert('输入不能为空')
      return
    }
    setPhoneList([
      {
        name: inputPhone,
        date: new Date().toString(),
      },
      ...phoneList,
    ])
    setInputPhone('')
    setModelShowTag(false)
  }
  // 类似于计算属性,来实现搜索,useMemo()
  const computedPhoneList = useMemo(() => {
    return phoneList.filter((item) => item.name.includes(searchInput))
  }, [phoneList, searchInput])
  return (
    <>
      {/* 这里是顶部显示的内容 */}
      <div className="header_con">
        <div className="header">
          <h1>手机品牌管理</h1>
          <input
            type="text"
            placeholder="请输入搜索条件"
            value={searchInput}
            onChange={(e) => {
              setSearchInput(e.target.value)
            }}
          />
        </div>
      </div>

      {/* 显示内容的标题部分 */}
      <div className="tb_title">
        <h3>品牌列表</h3>
        <button onClick={openModel}>新增品牌</button>
      </div>

      {/* 显示手机列表信息 */}
      <table className="tb">
        <thead>
          <tr>
            <th>编号</th>
            <th>品牌名称</th>
            <th>创立时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          {computedPhoneList.map((item, index) => {
            return (
              <tr key={'phone' + index}>
                <td>{index + 1}</td>
                <td>{item.name}</td>
                <td>{item.date}</td>
                <td>
                  <button onClick={() => removePhone(item.name)}>删除</button>
                </td>
              </tr>
            )
          })}

          {computedPhoneList.length <= 0 && (
            <tr>
              <td colSpan={4}>没有品牌数据</td>
            </tr>
          )}
        </tbody>
      </table>

      {/* 新增品牌弹框 */}
      {modelShowTag && (
        <div className="add_con">
          <div className="add">
            <h3>新增品牌</h3>
            <div className="add_form">
              <label>品牌名称:</label>
              <input
                type="text"
                value={inputPhone}
                onChange={(e) => {
                  setInputPhone(e.target.value)
                }}
                onKeyDown={(e) => {
                  enter(e)
                }}
              />
            </div>
            <div className="btns">
              <input type="button" value="取消" onClick={closeModel} />
              <input type="button" value="添加" onClick={addPhone} />
            </div>
          </div>
          <div className="mask"></div>
        </div>
      )}
    </>
  )
}

export default MyPhone

12、使用自定义 hooks

  1. 区别于自定义组件,自定义 hooks 的目的是是可以 逻辑复用,而组件是模块复用

    • 自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks
    • Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!
    • 自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)
  2. 当我们整个页面需要获取用户鼠标移动的坐标时,不使用hook的代码,我们可以这样写

import { useState, useEffect } from 'react'

export default function App() {
    const [position, setPosition] = useState({
        x: 0,
        y: 0
    })
    useEffect(() => {
        const move = (e:any) => {
            setPosition({ x: e.x, y: e.y })
        }
        document.addEventListener('mousemove', move)
        return () => {
            document.removeEventListener('mousemove', move)
        }
    }, [])
    return (
        <div>
            x 坐标:{position.x} --- y 坐标:{position.y}
        </div>
    )
}
  1. 当我们页面中有一个图片要跟随鼠标移动时,不使用hook的代码,我们也可以这样写:
import { useState, useEffect } from 'react'

export default function App() {
    const [position, setPosition] = useState({
        x: 0,
        y: 0
    })
    useEffect(() => {
        const move = (e: any) => {
            setPosition({ x: e.x, y: e.y })
        }
        document.addEventListener('mousemove', move)
        return () => {
            document.removeEventListener('mousemove', move)
        }
    }, [])
    return (
        <div>
            <img
                src="https://img0.baidu.com/it/u=1672615241,1947096361&fm=253&fmt=auto&app=138&f=PNG?w=192&h=192"
                style={{
                    position: 'absolute',
                    top: position.y - 50,
                    left: position.x - 50,
                }}
                alt="PIC"
            />
        </div>
    )
}
  1. 很明显,以上两个例子呈现效果不同,但使用的逻辑代码大部分相同时,这些逻辑代码我们就可以使用自定义 hook 进行逻辑复用,提取以上两个例子里可以复用的逻辑代码,新建一个名为 useMousePosition 的文件
// src/hooks/useMousePosition.ts
import { useState, useEffect } from 'react'
export default function useMousePosition() {
    const [position, setPosition] = useState({
        x: 0,
        y: 0
    })
    useEffect(() => {
        const move = (e: any) => {
            setPosition({ x: e.x, y: e.y })
        }
        document.addEventListener('mousemove', move)
        return () => {
            document.removeEventListener('mousemove', move)
        }
    }, [])
    return position
}
  1. 我们在 useMousePosition 函数中提取了此功能,现在,我们可以将其导入到要使用的任何位置!最后像使用普通函数那样使用即可
import useMousePosition from "./hooks/useMousePosition";
export default function App() {
    const position = useMousePosition()
    return (
        <div>
            <img
                src="https://img0.baidu.com/it/u=1672615241,1947096361&fm=253&fmt=auto&app=138&f=PNG?w=192&h=192"
                style={{
                    position: 'absolute',
                    top: position.y - 50,
                    left: position.x - 50,
                }}
                alt="PIC"
            />
        </div>
    )
}

13.react-router 路由

使用之前先安装对应的包,react-router-dom

声明式导航

  1. 使用前先需要使用HashRouter或者historyRouter对App组件进行包裹

    import { HashRouter } from 'react-router-dom'
    import App from './App'
    const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
    root.render(
      <React.StrictMode>
        <HashRouter>
          <App></App>
        </HashRouter>
      </React.StrictMode>
    )
    
  2. 在需要使用路由的地方进行引入,声明式导航类似于我们的a标签,也就说点击之后会跳转到不同的路由。在需要使用的地方先导入对应的路由以及组件

    {/* 第一步,引入组件以及路由 */}
    import { Home, Me, Index, About, List } from './pages/index'
    import { Route, Routes, Link } from 'react-router-dom'
    const App = () => {
      return (
        <>
          {/* 第三步: 声明导航,类似于a标签,要和底下的路由出口匹配 */}
          <nav>
            <Link to="/index">首页</Link>
            <Link to="/list">列表</Link>
            <Link to="/home">主页</Link>
            <Link to="/about">关于</Link>
            <Link to="/me">我的</Link>
          </nav>
          <h1 style={{ color: 'red' }}>我是公共父组件</h1>
       		{/*第二步: 声明路由出口 */}   
          <Routes>
            <Route path="/home" element={<Home></Home>}></Route>
            <Route path="/about" element={<About></About>}></Route>
            <Route path="/index" element={<Index></Index>}></Route>
            <Route path="/me" element={<Me></Me>}></Route>
            <Route path="/list" element={<List></List>}></Route>
          </Routes>
        </>
      )
    }
    
    export default App
    

编程式导航

编程式导航一般就是点击某个按钮,让其跳转到指定页面,可以传递参数。

使用useNavigate()来进行跳转,尽量不要在跟组件中使用编程式导航。

  1. 引入import {useNavigate} from 'react-router-dom'

  2. 创建navigate对象const navigate = useNavigate()

  3. 在需要使用的地方进行调用,比如跳转到home路由,则navigate('/home')

  4. 其中navigate(-1)是回退,也就是页面goback,navigate(1)是向前

  5. 可以接受第二个参数,replace表示之前组件的状态是否保存,state是要传递的数据

    navigate('/home',{
        //其中replace为true则说明是清除之前的所有组件信息,false则保留
        replate:false,
        //state则是要路由要传递的数据
        state:{
            title:'标题',
            data:'fd'
        }
    })
    

路由传参

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

React路由传参方式有三种:

动态路由(param)

  • 在 Route 中声明路由/film/detail/:id
<Route path='details/:type' element={<Details />} />
  • /film/detail/888形式传递的数据
<Link to='/details/telphone'>手机</Link>
<Link to='/details/computer'>电脑</Link>
  • 在目标页面落地组件中取 params 时需要借助 useParams()
import { useParams } from 'react-router-dom'
const params = useParams()

查询字符串(query)

  • 通过地址栏中的 ?key=value&key=value传递,不需要声明路由规则
<Link to='/details?type=telphone'>手机</Link>
<Link to='/details?type=computer'>电脑</Link>
  • 在取 search时需要借助 useSearchParams()
import { useSearchParams } from 'react-router-dom'
const [search]=useSearchParams()
const type = search.get("type")

式传参(state),通过地址栏是观察不到的

  • 不合适写在声明式导航中,写在编程式导航中更加合适
const navigate = useNavigate();
const goBack = () => {
	navigate(-1)
}
const goHome = () => {
	navigate('/home', {
		replace: false,
		state: {
			id: 8888,
			title: "state 传递标题",
			content: "state 传递内容"
		}
	})
}
  • 在落地组件中取 state,借助 useLocation(),刷新页面后数据依然可以存在
import { useLocation } from 'react-router-dom'
const location= useLocation()
const {id,title,content} = location.state
// 首页内容
export const Home = () => {
    const location = useLocation()
    // 需要判断是否传递参数,若未传递则进行兼容判断
    const state = location.state ? location.state : ''
    return (
        <section className="home">
            <h1>公司首页</h1>
            <p>首页内容,state 传递的数据是:{state && state.id} {state && state.title} {state && state.content}</p>
        </section>
    )
}

嵌套路由的使用

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

    例如,它们路由前缀的 home 是相同的,不同的只是后面一部份

http://localhost:3000/#/home/history
http://localhost:3000/#/home/persons
  1. 嵌套路由,直接嵌套在 父页面的路由内部
<Route path='/home' element={<Home />}>
	<Route path='history' element={<History />} />
	<Route path='persons' element={<Persons />} />
</Route>
  1. 可以定义索引路由,配置默认显示页面
<Route path='/home' element={<Home />}>
	{/* 索引路由 */}
	<Route index element={<History />} />
	<Route path='history' element={<History />} />
	<Route path='persons' element={<Persons />} />
</Route>
  1. 在落地页面中使用 Link 进行路由选择
  2. 在落地页面中使用 作为路由出口(类似于 Vue 的 router-view)
// src/pages/mypages.tsx
import { Link, Outlet, useLocation } from "react-router-dom";
// 首页内容
export const Home = () => {
    const location = useLocation()
    // 需要判断是否传递参数,若未传递则进行兼容判断
    const state = location.state ? location.state : ''
    return (
        <section className="home">
            <h1>公司首页</h1>
            <p>首页内容,state 传递的数据是:{state && state.id} {state && state.title} {state && state.content}</p>
            <nav>
                {/* 添加了四个导航组件Link */}
                <Link to='/home/history'>公司历史</Link>
                <Link to='/home/persons'>公司团队</Link>
            </nav>
            <Outlet />
        </section>
    )
}

重定向与404路由

  1. 重定向路由,例如首页的时候 不输入完整路由 /home 输入 / 即可访问首页
<Route path="/" element={<Navigate to="/home" />}></Route>
//  官方没有 redirect 组件,需要自定义 Redirect 组件
<Route path="*" element={<Redirect to="/film"/>}/>

function Redirect({to}){
    const navigate  =useNavigate()
    useEffect(() => {
        navigate(to,{replace:true})
    })
    return null
}
  1. 项目中少不了404页面的配置,在React里面配置404页面需要注意,在404路由的位置,不需要给定具体的路由匹配规则,不给path表示匹配*,即所有的路由都会匹配
// 404 页面
export const NotFound404 = () => (
    <div className="whoops-404">
        <h1>没有页面可以匹配</h1>
    </div>
)
// src/App.tsx
import { Routes, Route, Link, Navigate } from "react-router-dom";
import { Home, History, Persons, About, Contact, Products, Details, Events, NotFound404 } from "./pages/mypages";
import "./app.scss"
const App = () => {
  return (
    <div>
      {/* 页面公共部分 */}
      <h2>路由学习使用 - 公共部分 - 每个页面都有</h2>
      {/* 添加了四个声明式路由导航组件Link */}
      <nav>
        <Link to='/home'>公司首页</Link>
        <Link to='/about'>关于我们</Link>
        <Link to='/events'>企业事件</Link>
        <Link to='/products'>公司产品</Link>
        <Link to='/contact'>联系我们</Link>
      </nav>
      {/* 路由匹配渲染出口 */}
      <Routes>
        <Route path="/" element={<Navigate to="/home" />}></Route>
        <Route path='/home' element={<Home />}>
          {/* 索引路由 */}
          <Route index element={<History />} />
          <Route path='history' element={<History />} />
          <Route path='persons' element={<Persons />} />
        </Route>
        <Route path='/about' element={<About />} />
        <Route path='/contact' element={<Contact />} />
        <Route path='/products' element={<Products />} />
        <Route path='details/:type' element={<Details />} />
        <Route path='/events' element={<Events />} />
        <Route path='*' element={<NotFound404 />}></Route>
      </Routes>
    </div>
  );
};
export default App;

路由拦截

在 React 中路由拦截,需要使用到高阶组件的概念,比较灵活

<Route path="/center" element={
	<AuthComponent>
		<Center></Center>
	</AuthComponent>
}/>

function AuthComponent({children}){
 return localStorage.getItem("token")? children : <Redirect to="/login"/>
}
posted @ 2023-02-15 21:55  含若飞  阅读(33)  评论(0编辑  收藏  举报