React

1.类组件

类组件的状态数据定义在它的私有属性 state上

读取也是直接从state上读取

class ClassState extends Component{
   constructor(){
       //1. 调用父类的构造器
       super();
       //2. 定义状态数据
       this.state = {
           count:100,
           msg:'atguigu'
      }
  }
   render(){
       console.log('this.state: ',this.state);
       let {count, msg} = this.state;// 先解构在使用
       return (
           <div>
               <h3>Class 类组件 的状态数据的读取</h3>
               <p>count: {this.state.count} - {count}</p>
               <p>msg: {this.state.msg} - {msg}</p>
           </div>
      )
  }
}
export default ClassState;
import React, {Component} from 'react'

class ClassState extends Component{
   // 定义状态的方式二
   state = {
       count:1001,
       msg:'atguigu'
  }
   render(){
       console.log('this.state: ',this.state);
       let {count, msg} = this.state;// 先解构在使用
       return (
           <div>
               <h3>Class 类组件 的状态数据的读取</h3>
               <p>count: {this.state.count} - {count}</p>
               <p>msg: {this.state.msg} - {msg}</p>
           </div>
      )
  }
}
export default ClassState;

1.1修改状态数据及事件回调的this指向问题

this.setState(新的状态对象)

react的事件回调函数的调用者是 window,所以要想让事件回调函数中的this指向当前组件的实例对象,有以下手段:

  1. bind[推荐]:通过bind改变this指向,原理:使用的是render中的this

  2. 包裹箭头函数[推荐]:原理:也是使用的render中的this

  3. 直接定义成箭头函数[不推荐]:原理:使用的是constructor中的this

import React, {Component} from 'react'
class ClassState extends Component{
   // 定义状态的方式二
   state = {
       count:1001,
       msg:'atguigu'
  }
   addCount(){//
       // console.log('addCount this: ', this);
       // this.state.count += 1; // 直接给state赋值修改,虽然可以改变状态数据,但是无法触发视图重新渲染【没有触发render函数再调用】
       // console.log(this.state.count);

       // this.stateState
       this.setState({
           count: this.state.count + 1
      })
       /**
        * 事件回调函数 this指向问题:
        *
        * 1. bind
        * 2. 包裹箭头函数
        *
        * 3. 直接定义成箭头函数[不推荐]
        *
        * 推荐原则:
        * 1. 性能:是否多占用了内存空间
        * 2. 是否可以传参
        *
        */
  }

   addCount2 = ()=>{
       this.setState({
           count: this.state.count + 1
      })
  }
   render(){
       console.log('render run');
       console.log('this.state: ',this.state);
       let {count, msg} = this.state;// 先解构在使用
       return (
           <div>
               <h3>Class 类组件 的状态数据的读取</h3>
               <p>count: {this.state.count} - {count}</p>
               <p>msg: {this.state.msg} - {msg}</p>

               <p><button onClick={this.addCount.bind(this)}>count + </button></p>
               <p><button onClick={()=>this.addCount()}>count + </button></p>
               <p><button onClick={this.addCount2}>count + </button></p>
           </div>
      )
  }
}
export default ClassState;

1.2类组件事件回调的this指向问题

import React, { Component } from 'react'

export default class ClassState extends Component {
   state = {
       count: 99,
       msg: '尚硅谷'
  }

   constructor() {
       super();
       this.click3 = this.click3.bind(this);
  }
   click1() {
       // this.setState 运行后会产生两个严重后果
       // 1. 状态数据被改变了
       // 2. render函数重新调用

       // 报错:原因是事件的回调函数是 window调用的,所以this本身在严格模式下是指向undefined
       this.setState({
           count: this.state.count + 1
      })
  }
   click2(a,b,c) {
       // 因为使用bind,使得this用的是render中的this,render中this永远指向当前实例
       this.setState({
           count: this.state.count + 1
      })
  }
   click3() {
       this.setState({
           count: this.state.count + 1
      })
  }
   click4(a,b,c) {
       // onClick={()=>this.click4()}
       // 箭头函数没有自己的this,使用的是render中的this,所以click4中的this指向当前实例
       this.setState({
           count: this.state.count + 1
      })
  }

   click5 = () => {
       // 相当于是在构造函数construtor中执行的,所以使用的是constructor中的this
       this.setState({
           count: this.state.count + 1
      })
  }
   render() {
       console.log('render run');
       let { count, msg } = this.state;
       return (
           <div>
               <p>count : {count}</p>
               <p>msg : {msg}</p>
               <p><button onClick={this.click1}>count+ 有问题的</button></p>
               <p><button onClick={this.click2.bind(this,1,2,3)}>count + bind </button></p>
               <p><button onClick={this.click3}>count + bind + constructor</button></p>
               <p><button onClick={() => this.click4(11,22,33)}>包裹箭头函数</button></p>
               <p><button onClick={this.click5}>直接定义成箭头函数</button></p>
           </div>
      )
  }
}

1.3this.setState修改状态

使用方式有两种:

  1. 参数是对象:对象就是要设置的最新的状态

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

2.参数是回调函数:

2-1. 回调函数的参数可以取到最新的状态数据

2-2. 回调函数的返回值,就是设置的最新的状态

2-3. 回调函数是异步调用的

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

 

1.4外部数据props

props数据是父组件传递给子组件的数据,如果直接在子组件中修改了props就会造成跟父组件数据不同步。所以,react给props设置成了只读的。避免不一致情况的出现,如果想要修改,那么需要修改数据源,也就是修改父组件中的数据。那么就涉及到了一个新的知识点,子组件向父组件传递数据

父传子

1.4.1父传子

父组件:

import React from 'react'
import Son from "./components/Son";
/**
* 在一个组件中使用组件调用标签,调用另一个组件,那么就会产生父子组件的概念
* 父组件可以给子组件传递数据
* 1. 父组件如何传递数据给子组件?
*     在子组件的调用标签上,通过属性传递
*
* 2. 子组件中如何接收父组件传递的数据
*     this.props属性接收父组件传递的数据
*
* 在类组件中,如果父组件重新render渲染了,那么子组件也会无条件重新render渲染
*
*/
class App extends React.Component {
   state = {
       count: 1,
       msg: 'atguigu'
  }
   render() {
       console.log('App render');
       let {count, msg} = this.state;
       return (
           <>
               <h3>App</h3>
               <p>state count: {count}</p>
               <p>state msg: {msg}</p>
               <p><button onClick={()=>{
                   this.setState({
                       count: 100
                  })
              }}>count++</button></p>
               <hr />
               <Son count={count} msg={msg} school='尚硅谷'/>
           </>
      );
  }

}
export default App;

子组件:

import React, { Component } from 'react'

export default class Son extends Component {
   render() {
       console.log('Son render');
       // console.log('this.props: ', this.props); // 接收父组件传递的外部数据
       let {count, msg} = this.props;
       return (
           <div>
               <h3>Son 子组件</h3>
               <p>props count: {count}</p>
               <p>props msg: {msg}</p>

           </div>
      )
  }
}

1.4.2子传父

仍然是通过props传递,只不过传递的不是数据,而是一个函数

实现步骤:

  1. 在父组件中定义方法,并且改变该方法的this指向,永远指向父组件的实例对象

  2. 通过属性将该方法传递给子组件

  3. 在子组件中通过props接收该方法

  4. 在子组件中调用该方法,并通过实参传递数据

父组件

import React from 'react'
import Son from "./components/Son";

class App extends React.Component {
   state = {
       count: 1,
       msg: 'atguigu'
  }
   // 1. 父组件中定义方法
   addCount(num){
       console.log('num: ', num);
       this.setState({
           count: this.state.count + num
      })
  }
   render() {
       let {count, msg} = this.state;
       return (
           <>
               <h3>App</h3>
               <p>state count: {count}</p>
               <p>state msg: {msg}</p>
               <p><button onClick={()=>{
                   this.setState({
                       count: 100
                  })
              }}>count++</button></p>
               <hr />
              {/**2. 通过标签属性将方法传递给子组件, 并且要改变this指向,指向父组件的实例对象 */}
               <Son count={count} msg={msg} school='尚硅谷' addCount={this.addCount.bind(this)}/>
           </>
      );
  }

}
export default App;

 

子组件

import React, { Component } from 'react'

export default class Son extends Component {
   render() {
       let {count, msg, addCount} = this.props; // 子组件中接收父组件传递的方法addCount
       return (
           <div>
               <h3>Son 子组件</h3>
               <p>props count: {count}</p>
               <p>props msg: {msg}</p>

               <p><button onClick={()=>{
                   this.props.count += 1; // 直接修改props数据
                   // 报错:因为props数据是只读的,不可修改
              }}>props count++</button></p>
              {/** 子组件调用父组件传递的方法,并传递数据 */}
               <p><button onClick={()=>{
                   addCount(3)
              }}>子传父</button></p>

           </div>
      )
  }
}

1.5 props限定类型-默认值-必填

类组件:

static propTypes = {
   // count 必填,并且只能是数组
   // count:PropTypes.number.isRequired
   count: PropTypes.number,
   msg: PropTypes.string.isRequired
}
// 2. 设置默认值
static defaultProps = {
   count: 100
}

1.6ref获取真实DOM元素

import React, { Component } from 'react'
import { createRef } from 'react'

export default class App extends Component {
 inputRef=createRef();
 render() {
   return (
     <div>
       <input type="text" ref={this.inputRef}/>
       <button onClick={()=>{
             console.log(this.inputRef.current);
      }}>点击获取</button>
     </div>
  )
}
}

 

2.生命周期

分三个阶段:

  1. 挂载:componentDidMount 【重要】

时机:组件挂载完成之后执行 constructor -> render -> componentDidMount

操作:

1-1. 创建定时器

1-2. 发送ajax请求

1-3. 添加自定义事件

1-4. 订阅消息

  1. 更新:componentDidUpdate

时机:组件更新后执行

触发执行的三种方式:

2-1. new props

2-2. setState

2-3. forceUpdate

操作:

1. 发送ajax请求
1. 操作本地存储
  1. 卸载: componentWillUnmount 【重要】

时机:组件卸载前执行

操作:

3-1. 关闭定时器

3-2. 解绑自定义事件

3-3. 取消订阅消息

3.函数组件

3.1props数据

父组件通过 标签属性传递给函数子组件

函数子组件中通过 形参接收[一般是直接解构]

父组件App.jsx

import React from 'react'
import Son from './components/Son'
export default function App() {
   function getCount(count){
       console.log('App count: ',count);
  }
   return (
       <div>
           <h3>App</h3>
           <hr />
          {/* 通过标签属性传递数据 */}
           <Son count={1} msg={'啊疼硅谷个'} getCount={getCount}/>
       </div>
  )
}

子组件:components/Son.jsx

import React from 'react'

export default function Son({ count, msg, getCount }) {
   return (
       <div>
           <h3>Son</h3>
           <p>count: {count}</p>
           <p>msg: {msg}</p>
           <p><button onClick={()=>getCount(101)}>sendCount</button></p>
       </div>
  )
}

3.2props数据限定类型-必填-默认值

*
*  函数名.propTypes
*  函数名.defaultProps
*/
Son.propTypes = {
   count: PropTypes.number.isRequired,
   msg:PropTypes.string
}

Son.defaultProps = {
   msg:'尚硅谷'
}

4.CSS文件处理

css 分三类:

  1. 第三方的css: 比如:bootstrap

文件存放位置:public/*xx 例如 : public/css/bootstrap.css

引入方式:public/index.html 通过link标签引入

  1. 全局样式:

  2. 组件私有样式:

4.1第三方样式库导入

  • 文件存放位置:public/css/bootstrap.css

  • 文件导入位置:public/index.html

  • 导入的路径 必须 是 / ,/ 表示网站的根目录

    `<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" />`

4.2全局样式

reset css 或者是全局要使用的样式文件

位置:src/index.css 或者是 src/App.css

引入:

  1. src/index.css: src/index.js 中 import './index.css'引入

  2. src/App.css : src/App.jsx中 import './App.css'引入

4.3组件私有样式

位置: 组件目录/index.css

引入:组件中使用 import './index.css' 引入

注意:组件中的同名样式,会产生覆盖冲突

css模块化:将css 样式变成 js模块, 避免同名样式冲突被覆盖

  1. 样式文件名: xxx.module.css

  2. 导入:存入js变量中 import styles from './index.css'

  3. 使用样式:

  4. className={styles.class类名}

  5. className={[styles.class类名, '类名'].join(' ')}

5.图片处理

  1. 网络图片:直接填入网络地址即可

  2. 本地图片

注意:本地图片必须放在src目录内

2-1. require导入 【可以使用state 动态替换导入路径】

2-2. import导入

import React, { Component } from 'react'
import imgSrc from './assets/images/1.jpg'
export default class App extends Component {
   state = {
       index:1
  }
   render() {
       let {index} = this.state;
       return (
           <div>
               <h3>网络图片</h3>
               <img src="https://images.unsplash.com/photo-1520808663317-647b476a81b9?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=400&ixid=MnwxfDB8MXxyYW5kb218MHx8YW5pbWFsfHx8fHx8MTY4NTMzOTk2Ng&ixlib=rb-4.0.3&q=80&utm_campaign=api-credit&utm_medium=referral&utm_source=unsplash_source&w=500" alt="" />
               <h3>本地图片-require</h3>
               <img src={require(`./assets/images/${index}.jpg`)} alt="" />
               
               <h3>本地图片-import</h3>
               <img src={imgSrc} alt="" />
           </div>
      )
  }
}

6.类组件的受控组件

import React, { Component } from 'react'

export default class FormControl extends Component {
 state={
   username:'范腾龙',
   password:'123456'
}
 handlerChange(e){
   this.setState({
    [e.target.name]:e.target.value
  })
}
 submit(e){
     e.preventDefault();
   console.log(this.state.username,this.state.password);
}
 render() {
   let {username,password}=this.state;
   return (
    <form onSubmit={this.submit.bind(this)}>
           <p>用户名:<input type="text" name='username' value={username}  onChange={(e)=>this.handlerChange(e)}/></p>
           <p>密码: <input type="password" name='password'  value={password} onChange={(e)=>this.handlerChange(e)}/></p>
           <p><button type='submit'>注册</button></p>

    </form>
  )
}
}

7.类组件的非受控组件

import React, { Component, createRef } from 'react'

export default class FeControl extends Component {
 usernameRef=createRef();
 pwdRef=createRef();
 submit(e){
   e.preventDefault();
   console.log('username:',this.usernameRef.current.value);
   console.log('password:',this.pwdRef.current.value);
}
 render() {
   return (
    <form onSubmit={this.submit.bind(this)}>
<p>用户名:<input type="text" ref={this.usernameRef}/></p>
       <p>密码: <input type="password" name="" id="" ref={this.pwdRef}/></p>
       <p>
         <button>注册</button>
       </p>

    </form>
  )
}
}

 

8.函数组件

8.1useState

import React, { useState } from 'react'
import Son from './components/Son'
/*

hook函数(钩子函数)
useState:让函数组件拥有状态
语法:let【状态数据,设置状态的函数】=useState(状态初始值)
*/
export default function App() {
 let [count,setCount]=useState(10)
 return (
   <div>
     <p>count:{count}</p>
     <p><button onClick={()=>setCount(count+1)}>count+1</button></p>
     <hr />
     <Son count={count}></Son>
   </div>
)
}

8.2useEffect

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

export default function Son({count}) {
 let [age,setAge]=useState(16);
 useEffect(()=>{
   console.log('模拟componentDidMount');
},[]);
 useEffect(()=>{
   console.log('模拟componentDidMount+componentUpdate并监视age,count的变化');
},[age,count]);
 useEffect(()=>{
   console.log('模拟conponentDidMount+componentUpdate并监视所有值的变化');
})
 useEffect(()=>{
   console.log('模拟组件挂载的时候');
 return ()=>{
   console.log('模拟组件被卸载的时候');
}
},[])
 return (
   <div>
     <p>Son state count:{age}</p>
     <p><button onClick={()=>{setAge(age+1)}}>age++</button></p>
     <p>props count:{count}</p>
   </div>
)
}

8.3useRef

作用:创建一个ref对象, 一般用来绑定react元素,获取真实dom元素

语法: let xxRef = useRef(初始值);// xxRef : {current: 初始值}

import React, { useRef } from 'react'
export default function App() {
   let inputRef = useRef('sdkfj')
   // console.log('inputRef: ', inputRef);//{current: 'sdkfj'}
   return (
       <div>
           <input type="text" name="" id="" ref={inputRef}/>
           <button onClick={()=>{
               console.log('inputRef: ', inputRef);
               console.log('dom: ', inputRef.current);
               console.log('value: ', inputRef.current.value);
          }}>get Ref</button>
       </div>
  )
}

8.4函数受控组件

import React, { useState } from 'react'

export default function FormControl() {
   let [username,setUsername] = useState('atguigu');
   let [pwd, setPwd] = useState('123123')
   const changeUsername= (e)=>{
       setUsername(e.target.value);
  }
   const changePwd= (e)=>{
       setPwd(e.target.value);
  }

   const save = (e)=>{
       e.preventDefault();
       console.log('username: ', username);
       console.log('pwd: ', pwd);
  }
   return (
       <div>
           <form onSubmit={save}>
               <p>姓名: <input type="text" name="username" value={username} onChange={changeUsername}/></p>

               <p>密码: <input type="text" name="pwd" value={pwd} onChange={changePwd}/></p>
               <p><button>保存</button></p>
           </form>
       </div>
  )
}

9.组件间通信方式

9.1 props

props 父=》子 直接在组件标签中写属性进行传递

props 子=》父 在父组件定义一个函数传递给子组件,子组件接受并响应函数,通过实参传递

props 兄弟组件间通信 :利用它们共同的父亲传递函数实参传送数据

9.2 context

1.向外暴露一context

2.传送数据:利用context.provider组件上的value属性传递数据

3.接受数据:利用useContext(context)接收数据

context.js代码

import React from "react";
export default React.createContext();

App.jsx

import React from 'react'
import Father from './components/Father'
import context from './context'
export default function App() {
 return (
   <div>
     <context.Provider value={{username:'范腾龙',age:18}}>
     <Father></Father>
     </context.Provider>
   </div>
)
}

Son.jsx

import React from 'react'
import { useContext } from 'react'
import context from '../context'
export default function Son() {
 let {username,age}=useContext(context);
 return (
   <div>
     <p>username:{username}</p>
     <p>age:{age}</p>
   </div>
)
}

9.3pubsub-js(任意组件间通信)

1.首先安装 npm i pubsub-js

2.接收数据的组件中订阅消息

3.发送数据的组件中发布消息

接收数据组件代码

  const saveUsers=(msg,data)=>{
   setState(data)
};
//组件挂载完成钩子函数
useEffect(()=>{
 const token=PubSub.subscribe('saveUsers',saveUsers);
 return ()=>{
   PubSub.unsubscribe(token)
}
},[]);

发送数据组件代码

axios.get(`https://api.github.com/search/users?q=${value}`).then((response)=>{
       PubSub.publish('saveUsers', {
         users:response.data.items,
         isFirst:false,
         isLoading:false,
         err:''
      })
    }).catch((error)=>{
     saveUsers({
       users:[],
       isFirst:false,
       isLoading:false,
       err:error.message
    })
    })

10.useRef和useImperativeHandle

借助React.forwardRef()暴露子组件,让父组件的ref绑定到子组件标签上,子组件可以接收到父组件的ref并且可以借助useImperativeHandle去限制向外暴露的属性和方法

App.jsx

import React from 'react'
import { useRef } from 'react'
import Son from './components/Son';

export default function App() {
 const inputRef=useRef();
 return (
   <div>
     <h3>App</h3>
     <button onClick={()=>{
       inputRef.current.changeValue();
       inputRef.current.changStyle();
    }}>改变子组件真实DOM元素</button>
     <hr />
     <Son ref={inputRef}></Son>
   </div>
)
}

Son.jsx

import React from 'react'
import { useImperativeHandle } from 'react';
import { useRef } from 'react'

function Son(props,inputRef) {
 const SonRef=useRef();
 useImperativeHandle(inputRef,()=>{
   return {
     changeValue(){
       SonRef.current.value='我是李森我爱被虫子咬!'
    },
     changStyle(){
       SonRef.current.style.fontSize='25px'
    }
  }
})
 return (
   <div>
     <h3>Son</h3>
     <input type="text"  ref={SonRef}/>
   </div>
)
}
export default React.forwardRef(Son)

10.1useRef单独模拟componentDidUpdate周期

import React from 'react'
import { useRef } from 'react';
import { useState } from 'react'
import { useEffect } from 'react'

export default function App() {
 let [count,setCount]=useState(10);
 let flagRef=useRef(true)
 //单独模拟conponentDidUpdate
 useEffect(()=>{
   if(flagRef.current){
     flagRef.current=false;
     return
  }
     console.log('666');

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

11.useCallback[掌握]

每次函数组件更新,函数组件内的函数会重新创建.使用useCallBack可以缓存函数

import React, { useState, useCallback } from 'react'
export default function Test() {
 console.log('test渲染了')
 const [count, setCount] = useState(0)
 // 如果是空数组则回调函数只创建一次.如果不写第二个参数,或第二个参数监听数据,回调函数则创建多次
 const handle = useCallback(() => {
   console.log(count)
   setCount(count + 1)
}, [count])
 return (
   <div>
     <div>{count}</div>
     <button onClick={handle}>按钮</button>
   </div>
)
}

12.React.memo[掌握]

作用类似于类组件的PureComponent, 当父组件更新时,动态判断子组件使用更新

PureComponent: 当自身状态数据[state]和外部数据[props]没有变化的时候,组件不重新渲染

import React, { useState } from 'react'

function Son({msg}) {
   console.log('Son render');
   let [count, setCount] = useState(10)
   const addCount = () => {
       setCount(count => {
           return count + 1
      })
  }
   return (
       <div>
           <p>count: {count}</p>
           <p>props msg: {msg}</p>
           <p><button onClick={addCount}>count+</button></p>
           <p><button onClick={() => {
               setCount(999); // useState已经对状态不变的赋值做了优化,只多渲染一次
          }}>count = 999</button></p>
       </div>
  )
}
export default React.memo(Son)

13.useMemo[掌握]

会缓存一个计算的结果,如果没有变化,则不会重新执行计算

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

export default function WithMemo() {
   const [count, setCount] = useState(1);
   const [val, setValue] = useState('');

   const expensive =  useMemo(()=> { // expensive 昂贵的
       console.log('compute');
       let sum = 0;
       for (let i = 0; i < count * 100; i++) {
           sum += i;
      }
       return sum;
  },[count])

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

14.react路由

react路由概念:

  1. <BrowserRouter>: 组件包裹<App/>根组件,可以实现路由组件的切换

  2. Routes: 路由器

  3. Route: 路由: 配置 路径和 路由组件的映射关系

安装:react-router-dom

npm i react-router-dom

路由组件目录: pages或views

基本使用:

  1. 安装:npm i react-router-dom

  2. 导入BrowserRouter 并包裹 App根组件

// src/index.js
//1. 安装 npm i react-router-dom 2. 导入
import { BrowserRouter } from 'react-router-dom'
root.render(
   // 3. 包裹跟组件App
   <BrowserRouter>
       <App />
   </BrowserRouter>
);
  1. 创建路由组件

  例如:

 src/pages/Login.jsx

 src/pages/User.jsx

 src/pages/Home.js
  1. 配置路由映射

    // App.jsx

    {/*
      4. 配置路由 path 和路由组件的映射关系
          path 指定 路径
          element: 指定该路径对应展示的路由组件
    */}
    <Routes>
       <Route path='/login' element={<Login />}></Route>
       <Route path='/home' element={<Home />}></Route>
       <Route path='/user' element={<User />}></Route>
    </Routes>

5.浏览器地址栏访问测试

http://localhost:3000/login
http://localhost:3000/user
http://localhost:3000/home

 

14.1NavLink-Link使用及区别

NavLink-Link都可以生成无刷新的跳转链接,NavLink有高亮的样式类名,默认是active,Link没有

跳转的路径,都是通过 to属性进行指定

{/* NavLink 激活的路由有高亮样式类名 active【默认】 */}
<ul>
   <li><NavLink to='/login'>登录</NavLink></li>
   <li><NavLink to='/user'>用户中心</NavLink></li>
   <li><NavLink to='/home'>首页</NavLink></li>
</ul>
{/* Link可以生成无刷新的跳转链接,但是没有高亮样式类名 */}
<ul>
   <li><Link to='/login'>登录</Link></li>
   <li><Link to='/user'>用户中心</Link></li>
   <li><Link to='/home'>首页</Link></li>
</ul>

14.2自定义高亮样式类名

NavLink 的 className的值可以是一个回调函数,加载时会被自定义调用,回调函数的参数是一个对象,有一个属性isActive,是一个boolean,当当前路由处于激活状态时,值为true,失活时值为false

<ul>
   <li><NavLink className={(obj) => {
       // console.log('className', obj)
       return obj.isActive ? 'myselfActive' : ''
  }} to='/login'>登录</NavLink></li>
   <li><NavLink className={({isActive}) => isActive ? 'myselfActive' : ''} to='/user'>用户中心</NavLink></li>
   <li><NavLink className={({isActive}) => isActive ? 'myselfActive' : ''} to='/home'>首页</NavLink></li>
</ul>

14.3Navigate重定向配置

{/* 
  三种根目录 / 重定向配置方式
  配置后:访问 http://localhost:3000   http:localhost:3000
          会重定向到 http://localhost:3000/home
*/}
{/* <Route path='/' element={<Navigate to='/home' />}></Route> */}
{/* <Route index={true} element={<Navigate to='/home' />}></Route> */}
<Route index element={<Navigate to='/home' />}></Route>

14.4 404 页面配置

  1. 创建404 路由组件

  2. 配置

{/* 404 路径不存在的配置 */}
<Route path='*' element={<PageNotFound/>}></Route>

14.5二级路由页面练习

  1. 配置二级路由:二级路由是配置在一级路由 Route的对标签中

  2. 路径完整写法: path="/home/news"

  3. 路径简写: path="news" 注意:简写前面不能有 /

  4. 二级路由组件展示: <Outlet/>

14.6 路由表

路由表本质就是一个数组:记录了 path 和 element的映射关系

使用:

  1. 定义路由表

  2. 导入路由表

  3. 使用 useRoutes激活路由表

定义路由表: src/routes/index.js

import Home from '../pages/Home'
import About from '../pages/About'
import PageNotFound from '../pages/PageNotFound'
import News from '../pages/News'
import Message from '../pages/Message'
import { Navigate } from 'react-router-dom'
// 1. 配置路由表
const routes = [
  {
       path: '/home',
       element: <Home />,
       children:[// 配置子路由
          {
               // 完整写法
               path:'/home/news',
               element:<News/>
          },
          {
               // 简写
               path:'message',
               element:<Message/>
          },
          {
               path:'/home',
               element:<Navigate to='/home/news'/>
          }
      ]
  },
  {
       path: '/about',
       element: <About />
  },
  {
       index:true,
       // path:'/'
       element:<Navigate to='/home'/>
  },
  {
       path:'*',
       element:<PageNotFound/>
  }
]
export default routes;

导入并使用

import routes from './routes'

{useRoutes(routes)}

14.7-HashRouter

哈希路由模式:

<HashRouter>
<App/>
</HashRouter>

访问方式:http://localhost:3000/#/home/message

  • H5-history路由模式

<BrowserRouter>
<App/>
</BrowserRouter>

14.8路由组件懒加载

用户访问页面,往往只看一两个页面就退出了,所以没有必要一上来将所有的路由组件都加载。因此,希望有一个种方式,能够实现按需加载【用户看哪个路径,才去加载该路径对应的路由组件】

需要依赖一个 Suspense的组件和lazy函数,完成懒加载功能

  • 使用lazy函数导入组件

 // Suspense 和 lazy 是 react库中的组件和方法
import { Suspense, lazy } from 'react'

const About = lazy(() => import('../pages/About'))
const Message = lazy(() => import('../pages/Message'))
  • 使用Suspense组件,包裹懒加载的组件

function load(Com) {
   return (
       <Suspense fallback={<div>组件正在加载中....</div>}>
           <Com />
       </Suspense>
  )
}

 

14.9 编程式导航 useNavigate

什么是编程式导航?就是通过 js 进行路由跳转

编程式导航和 NavLink 、Link 有什么区别?

变成式导航可以添加权限验证等js逻辑,NavLink、Link不能

使用:

  1. 导入useNavigate 函数: import {useNavigate} from 'react-router-dom'

  2. 创建navigate跳转函数:

const navigate = useNavigate();
  1. 跳转

navigate('path');
例如:
navigate('/login')
navigate('/home')
navigate('/login', {
   replace:true // 不会生成新的历史记录,替换原来的历史记录
})

 

<div>
   <button onClick={()=>{
       // 编程式导航可以加入js逻辑,进行有条件的跳转
       // if(localStorage.getItem('token')) {
       //     navigate('/about')
       // }
       navigate('/about')
  }}>About history</button>
   <button onClick={()=>{
       navigate('/about', {replace:true}) // 替换掉当前的历史记录
  }}>About replace true</button>

   <button onClick={()=>navigate(1)}>前进</button>
   <button onClick={()=>navigate(-1)}>后退</button>

   <button onClick={()=>navigate(2)}>前进2步</button>
</div>

14.10路由参数传递和接收

可以通过路由进行参数的传递,参数类型有三种

携带参数的方式分别如下:

  1. params参数:通过路径携带 http://localhost:3000/home/message/1/atguigu

注意:params参数需要在路由表中配置参数占位符

  1. query参数:通过路径携带 http://localhost:3000/home/message?id=1&school=atguigu

  2. state参数:通过标签属性携带 , 参数不会出现在地址栏中

<Link to='detail' state={{id:1,school:atguigu}}>detail</Link>

路由组件中,接收参数的方式如下:

  1. params参数:let {id, school} = useParams()

  2. query参数:

const [search,setSearch] = useSearchParams()
const id = search.get('id')
const title = search.get('school')
  1. state参数:

const x = useLocation()
const {state: {id, school}} = x

 

15.redux

  1. 数据仓库: store

  2. 数据切片: slice

  3. 操作数据的方法: reducer

  4. 创建需求的方法:action creator

  5. 指派需求的方法: dispatch

15.1redux/toolkit 基本使用

安装: npm install @reduxjs/toolkit

// 1. 导入创建切片的函数
import {createSlice} from '@reduxjs/toolkit'

// 2. 创建切片
const countSlice = createSlice({
   name:'count',// 切片名
   initialState:{// 切片数据
       num:1
  },
   reducers:{ // 程序员之家,程序员操作数据,本质就是方法
       /**
        *
        * @param {*} state 切片状态数据
        * @param {*} action 需求 {type:'切片名/方法名', payload:操作数据的参数}
        *                       例如:{type:'count/addNum',payload:xxx}
        *
        * action中的type不是给程序员用的,而是redux自己使用的。
        * 我们一般只使用 action中的payload值,所以一般直接解构payload使用
        *
        * 每在reducers配置对象中创建一个程序员方法,会自动匹配一个同名的产品经理 action Creator. actionCreator在哪里呢? 在切片的actions属性上
        */
       addNum(state,action){
           // 直接给state中的状态赋值,即可改变切片数据
           state.num += 1;
      },
       decNum(state, {payload}){
           state.num -= payload
      }
  }
})

console.log(countSlice);

// 产品经理解构出来
const {addNum, decNum} = countSlice.actions;
// addNum decNum 身份是 actionCreator
// actionCreator 一执行,就会创建出一个action对象

console.log('addNum(5): ',addNum(5));
console.log('decNum(3): ',decNum(3));

15.2创建仓库

import {createSlice, configureStore} from '@reduxjs/toolkit'

const countSlice = createSlice({
   name:'count',
   initialState:{
       num:9
  },
   reducers:{
       addNum(state, action){//需求: {type:'count/addNum',payload:undefined}
           console.log('程序员 addNum 开始干活', action.payload)
           state.num += action.payload
      },
       decNum(state, {payload}){
           state.num -= payload
      }
  }
})

const {addNum, decNum} = countSlice.actions;// actionCreator 产品经理 创建需求
console.log(addNum(3));//{type: 'count/addNum', payload: 3}
console.log(decNum(5));//{type: 'count/decNum', payload: 5}
// 创建仓库
const store = configureStore({
   reducer:{
       count:countSlice.reducer
  }
})
// 获取仓库中所有的数据 getState
console.log(store.getState(), store.getState().count.num);
// 修改数据 仓库相当于是老板,老板     找     产品经理写一个需求文档,交给程序员干
//                           store dispatch actionCreator   action
store.dispatch(addNum(3)); // addNum(3) ==> {type:'count/addNum', payload:3}

console.log(store.getState());

15.3监听仓库变化

const unsubscribe = store.subscribe(()=>{})

import {createSlice, configureStore} from '@reduxjs/toolkit'

const countSlice = createSlice({
   name:'count',
   initialState:{
       num:9
  },
   reducers:{
       addNum(state, action){//需求: {type:'count/addNum',payload:undefined}
           console.log('程序员 addNum 开始干活', action.payload)
           state.num += action.payload
      },
       decNum(state, {payload}){
           state.num -= payload
      }
  }
})

const {addNum, decNum} = countSlice.actions;// actionCreator 产品经理 创建需求

console.log(addNum(3));//{type: 'count/addNum', payload: 3}

console.log(decNum(5));//{type: 'count/decNum', payload: 5}


// 创建仓库
const store = configureStore({
   reducer:{
       count:countSlice.reducer
  }
})

// 获取仓库中所有的数据 getState
console.log(store.getState(), store.getState().count.num);

// 修改数据 仓库相当于是老板,老板     找     产品经理写一个需求文档,交给程序员干
//                           store dispatch actionCreator   action
store.dispatch(addNum(3)); // addNum(3) ==> {type:'count/addNum', payload:3}
store.dispatch(decNum(2));
console.log(store.getState());

// 监听store仓库所有切片数据的变化,如果有变化,会触发回调函数的执行
// 返回值是取消监听的函数,函数执行后监听
const unsubscribe = store.subscribe(()=>{
   console.log(store.getState(),'1111');
})
store.dispatch(addNum(3)); // addNum(3) ==> {type:'count/addNum', payload:3}
unsubscribe();
store.dispatch(decNum(2));

15.4模块化

目录结构:

src
|-store                           redux仓库目录
|   |- slice                      切片目录
|   |    |- goodsSlice            商品切片
|   |    |- carSlice              购物车切片
|   |- index.js                   仓库

切片:暴露:

  1. 默认暴露 reducer

  2. 分别暴露 actionCreator

store/index.js

import { configureStore } from '@reduxjs/toolkit'
import goods from './slice/goodsSlice'
import car from './slice/carSlice'
// 创建仓库
const store = configureStore({
  reducer: {
      goods,
      car
  }
})

export default store;

store/slice/goodsSlice

import { createSlice } from '@reduxjs/toolkit'

// 1. 商品切片
const goodsSlice = createSlice({
  name: 'goods',
  initialState: {
      goodsList: [] // goods {id:xxx,gname:'小米',price:1999}
  },
  reducers: {
      // 添加购物车
      addGoods(state, { payload }) { // payload {gname:'xx', price:'xx'}
          state.goodsList = [...state.goodsList, {
              id: Math.random().toString(36).slice(2),
              ...payload
          }]
      }
  }
})
// 分别暴露 actionCreator
export const { addGoods } = goodsSlice.actions; // actionCreator获取完毕

// 默认暴露
export default goodsSlice.reducer

/**
* 切片需要暴露的内容
* 1. 默认暴露 reducer
* 2. 分别暴露 actionCreator
*/

store/slice/carSlice.js

import { createSlice } from '@reduxjs/toolkit'
// 购物车切片
const carSlice = createSlice({
  name: 'car',
  initialState: {
      carList: []
  },
  reducers: {
      // 添加购物车
      addCar(state, { payload }) { // payload {id,gname,price}
          /**
          * 如果购物车中有该商品,只累加数量,没有该商品,创建
          * 购物车中有没有该商品,如何判断?
          */
          let index = state.carList.findIndex(item => item.id === payload.id)
          if (index === -1) {
              state.carList = [...state.carList, {
                  ...payload,
                  buyNum: 1 // 购买数量
              }]
          } else {
              state.carList[index].buyNum += 1
          }
      }
  }
})

export const { addCar } = carSlice.actions
export default carSlice.reducer

15.5 react-redux

让redux能够更方便的在react项目中使用

使用步骤:

  1. 安装: npm i react-redux

  2. 使用Provider包裹App跟组件

import {Provider} from 'react-redux'
<Provider store={store}>
   <App/>
</Provider>
  1. 在组件中:使用 useSelector获取 store中的数据、使用useDispatch生成 dispatch函数

3.1-useSelector:

作用:获取store仓库中的数据

接收一个回调函数作为参数、useSelector(回调函数)

回调函数的参数,是store全部数据对象

回调函数的返回值,是useSelector 的返回值

例:

let { num } = useSelector(state => state.count);// 获取count切片中的num数据
let state = useSelector(state=>state); // 获取整个仓库的数据

3-2. useDispatch

作用:获得一个dispatch函数,功能跟 store.dispatch一样

例:

const dispatch = useDispatch();// 创建了一个dispatch函数

15.6异步操作

redux/toolkit中进行异步操作,首先需要使用createAsyncThunk创建异步的actionCreator,然后再切片的 extraReducers中定义异步的case

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'

const countSlice = createSlice({
   name: 'count',
   initialState: {
       num: 1
  },
   reducers: {
       addNum(state, { payload }) {
           state.num += payload
      },
       decNum(state, { payload }) {
           setTimeout(() => { // reducers中定义的方法,不支持异步操作
               state.num -= payload
          }, 1000)
      }
  },
   // extraReducers 中可以定义一些异步操作的方法
   // 先定义异步的actionCreator,使用 createAsyncThunk进行定义
   extraReducers: builder =>
       builder
  // asyncAddNum pending状态触发
          .addCase(asyncAddNum.pending, (state, action) => {
               console.log('asyncAddNum pending', action);
          })
  // asyncAddNum 成功的promise触发,并将成功的结果传递给payload
          .addCase(asyncAddNum.fulfilled, (state, { payload }) => {
               state.num += payload
          })
  // asyncAddNum 失败的promise触发
          .addCase(asyncAddNum.rejected, (state, action) => {
               console.log('rejected', action)
          })
})

// 创建异步的actionCreator,进行异步操作
export const asyncAddNum = createAsyncThunk('count/addNum', (payload) => {
   // 在此处可以进行异步操作,返回一个promise对象
   return new Promise((resolve, reject) => {
       setTimeout(() => {
           resolve(payload);
           // reject('error123')
      }, 2000)
  })
})
export default countSlice.reducer
export const { addNum, decNum } = countSlice.actions
 
posted @   前端4u  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示