react 18

react

1. 介绍

React是Meta公司研发,是一个用于构建Web和原生交互界面的库
在这里插入图片描述

2. 开发环境搭建

方式一:使用create-react-app快速搭建开发环境
create-react-app是一个快速 创建React开发环境的工具,底层由Webpack构建,封装了配置细节,开箱即用
执行命令:

npx create-react-app my-react-demp

1.npx Node.js: 工具命令,查找并执行后续的包命令
2.create-react-app: 核心包(固定写法),用于创建React项目
3. my-react-demp: React项目的名称(可以自定义)

看主要两个文件
在这里插入图片描述
在这里插入图片描述

方式二:使用vite快速搭建开发环境

3. JSX

3.1 什么是JSX

概念:JSX是JavaScript和XML(HTML)的缩写,表示在JS代码中编写HTML模版结构,它是React中编写UI模版的方式

优势:
1.HTML的声明式模版写法
2.JS的可编程能力
在这里插入图片描述

3.2 JSX的本质

JSX并不是标准的JS语法,它是JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行在这里插入图片描述

3.3 JSX基本语法

在JSX中可以通过 大括号语法{ }识别 JavaScrip中的表达式 ,比如常见的变量、函数调用、方法调用等等

JSX中使用JS表达式

1.使用引号传递字符串
2.使用JavaScript变量
3.函数调用和方法调用
4.使用JavaScript对象
注意:if语句、switch语句、变量声明属于语句,不是表达式,不能出现在{}中

示例代码:


const userName = "codeSE"
const getName = () => {
  return '上课就迟到'
}
const flag = false
function App () {
  return (
    <div className="App">
      <h1>
        MyApp
      </h1>
      <div>
        {/* 使用引号传递字符串 */}
        {"this is a test app"}
        {/* 使用js变量 */}
        {userName}
        {/* 使用函数和方法调用 */}
        {getName()}
        {new Date().getTime()}
        {/* 使用javascrpipt对象 */}
        <div style={{ width: '100px', height: '100px', color: 'red' }}>110</div>
        {/* 使用三元运算符 */}
        {flag ? '对' : '错'}
      </div>

    </div>
  )
}

export default App

JSX中实现列表渲染

示例代码:


//列表渲染
//map 重复渲染谁就return谁
//注意:key值,需要一个类型为number/string不可重复的key,提高diff性能

const loveList = [
    { id: 1, name: '爱情转移' },
    { id: 2, name: '爱情重点' },
    { id: 3, name: '爱情码头' },
    { id: 4, name: '爱情买卖' },
    { id: 5, name: '爱情终点' },
    { id: 6, name: '爱情舞曲' },
  
  
  ]
  function App() {
    return (
      <div className="App">
        <h1>
          {loveList.map(item => <p key={item.id}>{item.name}</p>)}
        </h1>
  
  
      </div>
    );
  }
  
  export default App;
  
JSX中实现条件渲染

在这里插入图片描述
示例代码:


//条件渲染
//技术方案:三元表达式、逻辑&&运算
//复杂的多分支逻辑,使用函数来写分支逻辑,模板只负责调用

const flag = true

function getTag (type) {
  if (type === 1) {
    return <h1> this is h1</h1>
  }
  if (type === 2) {
    return <h2> this is h2</h2>
  }
  if (type === 3) {
    return <h3> this is h3</h3>
  }
}

function App () {
  return (
    <div className="App">
      <h1>
        {flag ? (
          <div>
            <span>条件渲染</span>
          </div>
        ) : null}
      </h1>

      {false && <span>this is React</span>}

      {getTag(1)}
      {getTag(2)}
      {getTag(3)}

    </div>
  )
}

export default App

4. react事件绑定

基础语法: on+事件名称={事件处理程序},整体上遵循驼峰命名法

在这里插入图片描述

传递自定义参数
语法:事件绑定的位置改造成箭头函数的写法,在执行clickHandler实际处理业务函数的时候传递实参
在这里插入图片描述

同时传递事件对象和自定义参数
语法:在事件绑定的位置传递事件实参e和自定义参数,点击处理程序中声明形参,注意顺序对应
在这里插入图片描述
示例代码:

import React from 'react'

//1.函数组件创建和渲染
function Hello () {
  const clickHandle = (e) => {
    console.log('事件触发', e)
    //阻止a链接默认行为
    e.preventDefault()
  }
  return <a href='https:baidu.com' onClick={clickHandle}>a链接</a>
}
//传递额外参数
function Hello2 () {
  const clickHandle2 = (msg) => {
    console.log(msg)

  }
  return <div href='https:baidu.com' onClick={() => clickHandle2('code se 123')}>额外参数</div>
}
function Hello3 () {
  const clickHandle3 = (e, msg) => {
    console.log('事件触发:', e, '参数:', msg)


  }
  return <div onClick={(e) => clickHandle3(e, 'code se 123')}>事件和额外参数</div>
}

//类组件的创建和渲染
class HelloComponent extends React.Component {
  //事件回调函数(标准写法,避免this指向问题)
  //this执行当前的组件实列对象
  clickHander = () => {
    console.log('类组件事件触发!!!!')
  }
  render () {
    return <div onClick={this.clickHander}>HelloComponent</div>
  }
}
function App () {
  return (
    <div className="App">
      <Hello />
      <Hello></Hello>
      <Hello2></Hello2>
      <Hello3></Hello3>
      <HelloComponent />
      <HelloComponent></HelloComponent>
    </div>
  )
}

export default App

5. react组件

在React中,一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图U,渲染组件只需要把组件当成标签书写即可

在这里插入图片描述
示例代码:

import React from 'react'

//1.函数组件创建和渲染
function Hello () {
  return <div>hello react</div>
}

//类组件的创建和渲染
class HelloComponent extends React.Component {
  render () {
    return <div>HelloComponent</div>
  }
}
function App () {
  return (
    <div className="App">
      <Hello />
      <Hello></Hello>
      <HelloComponent />
      <HelloComponent></HelloComponent>
    </div>
  )
}

export default App

6. useState

useState 是一个 React Hook(函数),它允许我们向组件添加一个状态变量,从而控制影响组件的渲染结果
本质:和普通JS变量不同的是,状态变量一旦发生变化组件的视图U也会跟着变化(数据驱动视图)

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

7. react 样式处理

在这里插入图片描述

在这里插入图片描述
示例代码:


//jsx样式控制

//1.行内样式---- 在元素身上绑定一个style即可

//2.类名样式---- 在元素身上绑定一个className即可

import './app.css';
const myStyle = {
  color:'yellow',
  fontSize:'40px'
}
//动态样式,满足条件才有
const activeFlag = false;

function App() {
  return (
    <div className="App">
     
    <p style={{color:'red',fontSize:'30px'}}>hi测试v</p>
    <p style={ myStyle }>hi测试v</p>
      
    <p className='active'>类名测试</p>

    <p className={activeFlag?'active':''}>动态类名测试</p>

    </div>
  );
}

export default App;

8. 受控组件

受控表单绑定

概念:使用React组件的状态(useState)控制表单的状态
在这里插入图片描述
示例代码:

import { useState } from 'react'

//1.通过value属性绑定react状态
//2.绑定onChange事件 通过事件参数e拿到输入框最新的值 反向修改到react状态
function App() {
  const [value, setValue] = useState('')
  return (
    <div className="App">
      <input
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
      />
      绑定值:{value}
    </div>
  )
}
export default App

React 获取DOM

在这里插入图片描述
示例代码:


9. 组件通信

概念:组件通信就是组件之间的数据传递,根据组件嵌套关系的不同,有不同的通信方法

父传子

实现步骤:

  • 1.父组件传递数据-在子组件标签上绑定属性

  • 2.子组件接收数据-子组件通过props参数接收数据

  • 示例代码:

//父传子 props
import React from 'react'

//函数组件Son
function FunSon (props) {
  //props是一个对象,里面存着通过父组件传来的所有数据
  return (<div>函数组件:{props.msg}</div>)
}

//类组件Son
class MySon extends React.Component {
  //类组件必须通过this关键字,去获取这里props是固定
  render () {
    return (
      <div>
        类组件的:{this.props.msg}
      </div>
    )
  }
}



class App extends React.Component {
  //父组件数据
  state = {
    message: ' code se is good'
  }
  render () {
    return (
      <div>
        {/* 子组件绑定属性,属性名可以自定义,保持语义化 */}
        <FunSon msg={this.state.message} ></FunSon>
        <MySon msg={this.state.message}></MySon>
      </div >
    )
  }
}

export default App


父传子-props说明:
1.props可传递任意的数据数字、字符串、布尔值、数组、对象、函数、JSX
2.props是只读对象
子组件只能读取props中的数据,不能直接进行修改,父组件的数据只能由父组件修改
在这里插入图片描述
父传子-特殊的prop children

场景:当我们把内容嵌套在子组件标签中时,父组件会自动在名为children的prop属性中接收该内容
示例代码:

function FunSon(props) {
  console.log(props)
  return <div>这是:{props.children}</div>
}
function App() {
  return (
    <div>
      <FunSon>
        <p>codeSE</p>
      </FunSon>
    </div>
  )
}

export default App


子传父

在这里插入图片描述
示例代码:


import React from 'react'

//子传父 :子组件调用父组件传过来的函数,并把想传过来的函数当成函数的实参

function FunSon1 (props) {
  const { getMeg1 } = props

  return (
    <div>
      <button onClick={() => getMeg1('HHHHH')}>子组件传父1</button>
    </div>
  )
}
function FunSon2 (props) {
  const { getMeg2 } = props
  const a = 'BBBBBBBB'
  function handle () {
    getMeg2(a)
  }
  return (
    <div>
      <button onClick={handle}>子组件传父2</button>
    </div>
  )
}
class App extends React.Component {
  //使用函数传给子组件
  getMessage1 = (msg) => {
    console.log('来自子组件:', msg)
  }
  getMessage2 = (msg) => {
    console.log('来自子组件:', msg)
  }
  render () {
    return (
      <div>
        <FunSon1
          getMeg1={this.getMessage1}
        ></FunSon1>
        <FunSon2
          getMeg2={this.getMessage2}>
        </FunSon2>
      </div >
    )
  }
}

export default App


使用状态提升实现兄弟组件通信

在这里插入图片描述
示例代码:


import React from 'react'

//兄弟组件之间传值

function SonA (props) {
  return (
    <div>
      A:{props.sendMsg}
    </div>
  )
}
function SonB (props) {
  const { getMsg } = props
  const b = 'B组件的数据'

  return (
    <div>
      B
      <button onClick={() => getMsg(b)}>组件</button>
    </div>
  )
}
class App extends React.Component {
  state = {
    message: "A组件初始值"
  }

  getMsgA = (b) => {
    console.log(b)
    this.setState({
      message: b
    })
  }
  render () {
    return (
      <div>
        <SonA sendMsg={this.state.message}

        ></SonA>
        <SonB getMsg={this.getMsgA}>

        </SonB>
      </div >
    )
  }
}

export default App


使用context跨层传递数据

在这里插入图片描述
示例代码:


import React, { createContext } from 'react'

//语法固定:提供位置由value提供数据,获取位置{value => <tag> {value}</tag>}做接收

//1. 创建Context对象,提供者和消费者 
const { Provider, Consumer } = createContext()

function AComponent () {
  return (
    <div> this is A
      <CComponent></CComponent>
    </div>
  )
}
function CComponent () {
  return (
    //3、通过Consumer使数据
    <div>
      this is C
      <Consumer>
        {value => <span> {value}</span>}
      </Consumer>
    </div>

  )
}

class App extends React.Component {

  state = {
    message: 'this is code SE'
  }
  render () {

    return (
      //2。使用Provider包裹根组件
      <Provider value={this.state.message}>
        <div>
          <AComponent></AComponent>
        </div>
      </Provider>


    )
  }
}

export default App


10. useEffect 的使用

概念:useEffect是一个React Hook函数,用于在React组件中创建不是由事件引起而是由渲染本身引起的操作,比如发送AJAX请求,更改DOM等等
在这里插入图片描述
在这里插入图片描述

useEffect 依赖项参数说明

useEffect副作用函数的执行时机存在多种情况,根据传入依赖项的不同,会有不同的执行表现
在这里插入图片描述
示例代码:


import { useState, useEffect } from 'react'
//在修改数据之后,把count值放在页面标题中
//1、导入useEffect函数
//2、在 函数组件中执行, 传入回调 并且定义副作用
//3、当修改状态值更新组件时,副作用也会不断执行

/**
 * 依赖项控制副作用的执行时机
 * 1.默认状态(无依赖项)
 * 组件初始化时执行一次,等到每次数据更新再次执行
 * 
 * 2.添加空数组依赖项
 * 组件初始化时执行一次,后面更新不在执行了
 * 
 * 3.依赖特定项
 * 组件初始化时执行一次,依赖的特定项发生变化会再次执行
 * 
 * 4.注意事项
 * 只要在useEffect回调函数中用到的数据状态就应该出现在依赖项数组中,否则会有bug
 * 
 * 某种意义上:hooks的出现,就是想不用生命周期概念也可以写业务代码
 */

 
function App () {
  const [count, setCount] = useState(0)
  const [name, setName] = useState('HH')
  useEffect(() => {
    //定义副作用
    console.log('执行了副作用')
    document.title = count
  }, [count])
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}+</button>
      <button onClick={() => setName('SE')}>{name}+</button>

    </div>
  )
}
export default App

useEffect——清除副作用

在useEffect中编写的由渲染本身引起的对接组件外部的操作,社区也经常把它叫做副作用操作,比如在useEffect中开启了一个定时器,我们想在组件卸载时把这个定时器再清理掉,这个过程就是清理副作用
在这里插入图片描述
示例代码:


import { useState, useEffect } from 'react'

/**
 * Hooks 进阶

实现步骤
1. useEffect  return()=>{}
2. 
3. 
 */

function Test () {

  useEffect(() => {
    let timer = setInterval(() => {
      console.log('定时器')
    }, 1000)
    return () => {
      //清理动作(组件卸载时)
      clearInterval(timer)
    }
  }, [])

  return (
    <div>
      this is code SE!
    </div>
  )
}

function App () {
  const [flag, setFlag] = useState(true)
  return (
    <>
      {flag ? <Test></Test> : null}
      <button onClick={() => setFlag(!flag)}>{flag ? '隐藏组件' : '显示组件'}</button>
    </>
  )
}

export default App

11. 自定义Hook函数

概念:自定义Hook是以 use 打头的函数,通过自定义Hook函数可以用来实现逻辑的封装和复用

自定义hook:

import { useState, useEffect } from 'react'
/*封装自定义hook通用思路
1.声明一个以use打头的函数
2.在函数体内封装可复用的逻辑(只要是可复用的逻辑)
3.把组件中用到的状态或者回调return出去(以对象或者数组)
4.在哪个组件中要用到这个逻辑,就执行这个函数,解构出来状态和回调进行使用
*/
export function useLocalStorage (key, value) {
  const [message, setMessage] = useState(value)

  //只要数据有变化就自动同步到本地存储
  useEffect(() => {
    window.localStorage.setItem(key, message)
  }, [message, key])

  return [message, setMessage]
}

使用:


import { useLocalStorage } from './hooks/useLocalStorage'


function App () {
  const [message, setMessage] = useLocalStorage('hook-key', 'Code SE')

  setTimeout(() => {
    setMessage('XLXC')
  }, 3000)
  return (
    <div style={{ height: '1200px' }}>

      {message}

    </div>
  )
}
export default App

React Hooks使用规则

使用规则
1.只能在组件中或者其他自定义Hook函数中调用
2.只能在组件的顶层调用,不能嵌套在if、for、其他函数中

以下写法不允许:
在这里插入图片描述

12.Redux—状态管理工具

Redux是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行

作用:通过集中管理的方式管理应用的状态
在这里插入图片描述

Redux快速体验

不和任何框架绑定,不使用任何构建工具,使用纯Redux实现计数器
使用步骤:
1.定义一个 reducer 函数 (根据当前想要做的修改返回一个新的状态)
2.使用createStore方法传入reducer函数 生成-个store实例对象
3.使用store实例的 subscribe方法 订阅数据的变化(数据一旦变化,可以得到通知)
4.使用store实例的 dispatch方法提交action对象 触发数据变化(告诉reducer你想怎么改数据)
5.使用store实例的 getState方法 获取最新的状态数据更新到视图中

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button id="decrement">-</button>
  <span id="count">0</span>
  <button id="increment">+</button>
  <script src="https://cdn.jsdelivr.net/npm/redux@latest/dist/redux.js"></script>
  <script>
    // 1.定义reducer函数
    // 作用:根据不同的action对象,返回不同的新的state
    // state:管理的数据初始状态
    // action:对象 type 标记当前想要做什么样的修改
    function reducer (state = { count: 0 }, action) {
      //数据不可变:基于原始状态生成一个新的状态
      if (action.type == "ADD") {
        return { count: state.count + 1 }
      }
      if (action.type == "DEC") {
        return { count: state.count - 1 }

      }
      return state
    }

    //2.使用reducer函数生成store实例
    const store = Redux.createStore(reducer)

    //3.通过store实例的subscribe订阅数据变化
    // 回调函数可以在每次state发生变化的时候自动执行
    store.subscribe(() => {
      console.log("数据变化了:", store.getState())
      document.getElementById("count").innerHTML = store.getState().count
    })


    // 4.通过store实例的dispatch函数提交action更改状态
    const inBtn = document.getElementById('increment')
    inBtn.addEventListener('click', () => {
      // 增
      store.dispatch({
        type: "ADD"
      })
    })
    const dBtn = document.getElementById('decrement')
    dBtn.addEventListener('click', () => {
      // 减
      store.dispatch({
        type: "DEC"
      })
    })
    //5.通过store实例的getState方法获取最新状态更新到视图中
  </script>
</body>

</html>

Redux管理数据流程梳理

在这里插入图片描述

为了职责清晰,数据流向明确,Redux把整个数据修改的流程分成了三个核心概念,分别是:state、action和reducer
1.state-一个对象 存放着我们管理的数据状态
2.action-一个对象 用来描述你想怎么改数据
3.reducer–个函数 更具action的描述生成一个新的state

Redux与React-环境准备

配套工具
在React中使用redux,官方要求安装俩个其他插件-ReduxToolkit和react-redux
1.Redux Toolkit(RTK)-官方推荐编写Redux逻辑的方式,是一套工具的集合集,简化书写方式
在这里插入图片描述
2.react-redux-用来链接 Redux和 React组件 的中间件
在这里插入图片描述

安装配套工具
npm i @reduxjs/toolkit react-redux

store目录结构设计
在这里插入图片描述

1.通常集中状态管理的部分都会单独创建一个单独的store目录
2.应用通常会有很多个子store模块,所以创建一个modules目录,在内部编写业务分类的子store
3.store中的入口文件 index.js 的作用是组合modules中所有的子模块,并导出store
在这里插入图片描述
步骤一:store/modelus/counterStore.js

import { createSlice } from "@reduxjs/toolkit"
const counterStore = createSlice({
  name: 'counter',
  // 初始状态数据
  initialState: {
    count: 0
  },
  // 修改数据的同步方法
  reducers: {
    increment (state) {
      state.count++
    },
    decrement (state) {
      state.count--

    },
    addTonum (state, action) {
      state.count = action.payload
    }
  }

})

// 解构出创建action对象的函数
const { increment, decrement, addTonum } = counterStore.actions
// 获取reducer函数
const reducer = counterStore.reducer

//以按需导出的方式导出actionCreater
export { increment, decrement, addTonum }
//以默认导出的方式导出reducer
export default reducer

步骤二:store/index.js

//导入配置方法
import { configureStore } from "@reduxjs/toolkit"
//导入子模块
import counterReducer from './modules/counterStore'

//根store
const store = configureStore({
  //聚合子模块
  reducer: {
    counter: counterReducer,
  }
})
//导出根
export default store

步骤三:为React注入store
react-redux负责把Redux和React链接 起来,内置 Provider组件 通过 store 参数把创建好的store实例注入到应用中链接正式建立

import React from 'react'//React:框架核心包
import ReactDOM from 'react-dom/client'//专门做渲染的包
import store from './store'
import { Provider } from 'react-redux'

//渲染根组件到id为root的节点上
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  //严格模式节点需要去掉 userEffect的执行时机
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
 
);

步骤四:React组件使用store中的数据和修改store中的数据
在React组件中使用store中的数据,需要用到一个钩子函数-useSelector,它的作用是把store中的数据映射到组件使用
React组件中修改store中的数据需要借助另外一个hook函数-useDispatch,它的作用是生成提交action对象的dispatch函数
使用样例如下:

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

//导入actionCreater
import { increment, decrement } from './store/modules/counterStore'
function App() {
  const { count } = useSelector((state) => state.counter)
  const dispatch = useDispatch()
  return (
    <>
      <div>来自redux状态的数据:{count}</div>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(increment())}>+</button>
    </>
  )
}
export default App

提交action传参实现需求:

在reducers的同步修改方法中 添加action对象参数 ,在 调用actionCreater的时候传递参数 ,参数会被传递到action对象 payload属性上
在这里插入图片描述

在这里插入图片描述
1.创建store的写法保持不变,配置好同步修改状态的方法
2.单独封装一个函数,在函数内部return一个新函数,在新函数中

  • 2.1 封装异步请求获取数据
  • 2.2 调用同步actionCreater传入异步数据生成一个action对象,并使用dispatch提交

3.组件中dispatch的写法保持不变

store/channelStore.js

import { createSlice } from "@reduxjs/toolkit"
import axios from "axios"
const channelStore = createSlice({
  name: "channel",
  initialState: {
    channelList: []
  },
  reducers: {
    setChannels (state, action) {
      state.channelList = action.payload
    }
  }
})
const { setChannels } = channelStore.actions
//异步请求部分
const fetchChannelList = () => {
  return async (dispatch) => {
    const res = await axios.get("http://geek.itheima.net/v1_0/channels")
    dispatch(setChannels(res.data.data.channels))
  }

}
export { fetchChannelList }
const reducer = channelStore.reducer

export default reducer

组件内使用

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

//导入actionCreater
import { increment, decrement, addTonum } from './store/modules/counterStore'
import { fetchChannelList } from './store/modules/channelStore'
function App() {
  const { count } = useSelector((state) => state.counter)
  const { channelList } = useSelector((state) => state.channel)

  const dispatch = useDispatch()

  //使用useEffect触发异步请求执行
  useEffect(() => {
    dispatch(fetchChannelList())
  }, [dispatch])
  return (
    <>
      <div>来自redux状态的数据:{count}</div>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(addTonum(10))}>10</button>
      <button onClick={() => dispatch(addTonum(20))}>20</button>

      <ul>
        {channelList.map((item) => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </>
  )
}
export default App


13. ReactRouter—路由

什么是前端路由

一个路径 path 对应一个组件 component 当我们在浏览器中访问一个 path 的时候,path 对应的组件会在页面中进行渲染

安装最新的 ReactRouter包
npm i react-router-dom

快速使用:

import React from 'react'//React:框架核心包
import ReactDOM from 'react-dom/client'//专门做渲染的包
import { createBrowserRouter,RouterProvider } from "react-router-dom"//路由模块

//创建router实例对象,并配置路由对应关系
const router = createBrowserRouter([
  {
    path: '/login',
    element: <div>我是登录页面</div>
  },
  {
    path: '/home',
    element: <div>我是首页页面</div>
  },
 
])

//渲染根组件到id为root的节点上
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
  //.使用路由
  <React.StrictMode>
      <RouterProvider router={router}></RouterProvider>
  </React.StrictMode>
);

在这里插入图片描述

实际开发使用配置:

  1. 通常新建一个router文件夹(类似vue)
import { createBrowserRouter } from "react-router-dom"
import Login from "../pages/Login/index.jsx"
import Home from "../pages/Home/index.jsx"


//创建router实例对象,并配置路由对应关系
const router = createBrowserRouter([
  {
    path: '/login',
    element: <Login></Login>
  },
  {
    path: '/home',
    element: <Home></Home>
  },
  {
    path: '/hello',
    element: <div>hello word</div>
  },
])

export default router

  1. 在入口文件导入使用

ReactRouter-路由导航

什么是路由导航

路由系统中的多个路由之间需要进行路由跳转,并且在跳转的同时有可能需要传递参数进行通信

声明式导航
  • 声明式导航是指通过在模版中通过`组件描述出要跳转到哪里去,比如后台管理系统的左侧菜单通常使用这种方式进行
  • 语法说明:通过给组件的to属性指定要跳转到路由path,组件会被渲染为浏览器支持的a链接,如果需要传参直接通过字符串拼接的方式拼接参数即可
  • 在这里插入图片描述
编程式导航
  • 编程式导航是指通过useNavigate钩子得到导航方法,然后通过调用方法以命令式的形式进行路由跳转,比如想在登录请求完毕之后跳转就可以选择这种方式,更加灵活
  • 语法说明:通过调用navigate方法传入地址path实现跳转
  • 在这里插入图片描述

示例代码:

import { Link, useNavigate } from 'react-router-dom'

const Home = () => {
  const navigate = useNavigate()

  return (
    <div>
      我是首页页面
      {/*  声明式导航 */}
      <Link to="/login">去登录</Link>
      {/* 编程式导航 */}
      <button onClick={() => navigate('/login')}>去登录</button>
    </div>
  )
}
export default Home

ReactRouter-导航传参

searchParams传参params传参
在这里插入图片描述

import { Link, useNavigate, useSearchParams } from 'react-router-dom'
const Login = () => {
  const navigate = useNavigate()

  //searchParams接惨
  const [params] = useSearchParams()
  console.log(params)
  let id = params.get('id')
  let name = params.get('name')

  return (
    <>
      <h1> 我是登录页面</h1>
      <div>
        <button>
          <Link to="/home">去首页</Link>
        </button>
        <button onClick={() => navigate('/home/10086')}>
          路由传参params到首页
        </button>
        <div>
          useSearchParams路由参数:{id}——{name}
        </div>
      </div>
    </>
  )
}

export default Login

import { Link, useNavigate, useParams } from 'react-router-dom'

const Home = () => {
  const navigate = useNavigate()
  //Params接惨
  let params2 = useParams()
  console.log(params2)
  let number = params2.number

  return (
    <>
      <h1>我是首页页面</h1>
      <div>
        {/*  声明式导航 */}
        <Link to="/login">去登录</Link>
        {/* 编程式导航 */}
        <button onClick={() => navigate('/login')}>去登录</button>
        <button onClick={() => navigate('/login?name=codeSE&id=110')}>
          searchParams路由传参到登录
        </button>
        <div>Params接惨:{number}</div>
      </div>
    </>
  )
}
export default Home

使用Params时注意在路由配置占位

在这里插入图片描述

嵌套路由

嵌套路由配置
实现步骤:

  • 1.使用 children 属性配置路由嵌套关系
  • 2.使用<Outlet/>组件配置二级路由渲染位置
  • 在这里插入图片描述
- 
默认二级路由

当访问的是一级路由时,默认的二级路由组件可以得到渲染,只需要在二级路由的位置去掉path,设置index属性为true

404路由

场景:当浏览器输入url的路径在整个路由配置中都找不到对应的 path,为了用户体验,可以使用 404兜底组件进行渲染

实现步骤

1.准备一个NotFound组件
2.在路由表数组的末尾,以*号作为路由path配置路由

ReactRouter-两种路由模式

两种路由模式
各个主流框架的路由常用的路由模式有俩种,history模式和hash模式,ReactRouter分别由 createBrowerRouter 和createHashRouter函数负责创建

在这里插入图片描述

本节完整代码:

router.js:

import { createBrowserRouter } from "react-router-dom"
import Login from "../pages/Login/index.jsx"
import Home from "../pages/Home/index.jsx"
import Layout from "../pages/Layout/index.jsx"
import About from "../pages/About/index.jsx"
import Board from "../pages/Board/index.jsx"
import NotFound from "../pages/NotFound/index.jsx"


//创建router实例对象,并配置路由对应关系
const router = createBrowserRouter([
  {
    path: '/login',
    element: <Login></Login>
  },
  {
    path: '/home/:number',
    element: <Home></Home>
  },
  {
    path: '/',
    element: <Layout></Layout>,
    children: [
      {
        index: true, //设置默认二级路由
        element: <Board />
      },
      {
        path: "about",
        element: <About />
      },
    ]
  },
  {
    path: '*',
    element: <NotFound></NotFound>
  },
])

export default router

layout.js

import { Link, Outlet } from 'react-router-dom'
const Layout = () => {
  return (
    <>
      <h1>我是一级路由</h1>
      <Link to="/">面板</Link>
      <Link to="/about">关于</Link>
      {/* 配置二级路由的出口 */}
      <Outlet></Outlet>
    </>
  )
}

export default Layout

在这里插入图片描述

14. react项目中配置

1.别名路径配置

1.路径解析配置(webpack),把 @/ 解析为 @
2.路径联想配置(VsCode),VsCode 在输入@/时,自动联想出来对应的 src/下的子级目录

路径解析配置

CRA本身把webpack配置包装到了黑盒里无法直接修改,需要借助一个插件craco

配置步骤:

  • 1.安装craco
    npm i -D @craco/craco
  • 2.项目根目录下创建配置文件
    craco.config.js
  • 3.配置文件中添加路径解析配置
  • 4.包文件中配置启动和打包命令
    在这里插入图片描述

联想路径配置
VsCode的联想配置,需要我们在项目目录下添加jsconfig.json 文件,加入配置之后VsCode会自动读取配置帮助我们自动联想提示
配置步骤:

  • 1.根目录下新增配置文件-jsconfig.json
  • 2.添加路径提示配置
    在这里插入图片描述

15. 前端使用Mock数据

在前后端分类的开发模式下,前端可以在没有实际后端接口的支持下先进行接口数据的模拟,进行正常的业务功能开发

json-server实现数据Mock

json-server是一个node包,可以在不到 30秒内获得零编码的完整的Mock服务

实现步骤

  • 1.项目中安装json-server
    npm i -D json-server
  • 2.准备一个json文件
  • 3.添加启动命令
    "server":"json-server ./server/data.json --port 8888"
  • 4.访问接口进行测试
    在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

16. 创建项目目录规范

在这里插入图片描述
打包优化-CDN优化
什么是CDN?
CDN是一种内容分发网络服务,当用户请求网站内容时,由离用户最近的服务器缓存的资源内容传递给用户
哪些资源可以放到CDN服务器?
体积较大的非业务IS文件,比如react、react-dom1.体积较大,需要利用CDN文件在浏览器的缓存特性,加快加载时间2.非业务|S文件,不需要经常做变动,CDN不用频繁更新缓存
项目中怎么做?
1.把需要做CDN缓存的文件排除在打包之外(react、react-dom)2.以CDN的方式重新引入资源(react、react-dom)

17 . react常用的API

useReducer

作用:和useState的作用类似,用来管理相对复杂的状态数据

useReducer-基础用法:

  • 1.定义一个reducer函数(根据不同的action返回不同的新状态
  • 2.在组件中调用useReducer,并传入reducer函数和状态的初始值
  • 3.事件发生时,通过dispatch函数分派一个action对象(通知reducer要返回哪个新状态并渲染UI)

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

示例代码:

import { useReducer } from 'react'
//useReducer使用

//1.定义reducer函数 根据不同的action 返回不同的状态
function reducer(state, action) {
  switch (action.type) {
    case 'INCREASE':
      return state + 1
    case 'DECREASE':
      return state - 1
    case 'SET':
      return action.payload
    default:
      return state
  }
}

//2.组件中调用useReducer(reducer,0)=>[state,dispatch]

//3.调用dispatch({type:'INC'})=>通知reducer产生一个新的状态 使用这个新状态更新UI

function App() {
  const [state, dispatch] = useReducer(reducer, 0)
  return (
    <>
      <div>react-app</div>
      <button onClick={() => dispatch({ type: 'INCREASE' })}>+</button>
      <button onClick={() => dispatch({ type: 'DECREASE' })}>-</button>
      <button onClick={() => dispatch({ type: 'SET', payload: 100 })}>
        更新
      </button>

      <p>{state}</p>
    </>
  )
}
export default App

useMemo

作用:在组件每次重新渲染的时候 缓存计算的结果

useMemo-基础语法
在这里插入图片描述

说明:使用useMemo做缓存之后可以保证只有count1依赖项发生变化时才会重新计算

示例代码:


import { useMemo, useState } from 'react'
//useMemo使用
//缓存:消耗非常大的计算才考虑使用
function sum(n) {
  console.log('计算函数执行了...')
  if (n < 3) {
    return 1
  } else {
    return sum(n - 2) + sum(n - 1)
  }
}

function App() {
  const [count1, setCount1] = useState(0)

  //只针对count1变化时调用
  // const result = sum(count1)//错
  const result = useMemo(() => {
    //返回计算结果
    return sum(count1)
  }, [count1])

  const [count2, setCount2] = useState(0)
  console.log('组件重新渲染了...')

  return (
    <>
      <div>react-app</div>
      <button onClick={() => setCount1(count1 + 1)}>增加:{count1}</button>
      <button onClick={() => setCount2(count2 + 1)}>减少:{count2}</button>
      <h3>结果:{result}</h3>
    </>
  )
}
export default App

memo

作用:允许组件在Props没有改变的情况下跳过渲染
React组件默认的渲染机制:只要父组件重新渲染子组件就会重新渲染

思考:如果Son组件本身并不需要做渲染更新,是不是存在浪费?
React.memo-基础语法
在这里插入图片描述
说明:经过memo函数包裹生成的缓存组件只有在props发生变化的时候才会重新渲染

示例代码:

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

//1.验证默认的渲染机制子跟着父一起渲染
function Son(n) {
  console.log('子组件渲染了...')
  return <div>this is son</div>
}
//2.memo进行缓存,只有props发生变化的时候才会重新渲染(context)
const MemoSon = memo(function Son() {
  console.log('子组件渲染了...')
  return <div>this is son</div>
})
function App() {
  const [count, setCount] = useState(0)
  console.log('父组件渲染了...')

  return (
    <>
      {/* <Son></Son> */}
      <MemoSon></MemoSon>
      <button onClick={() => setCount(count + 1)}>增加:{count}</button>
    </>
  )
}
export default App

React.memo-props的比较机制

机制: 在使用memo缓存组件之后,React会对每一个 prop 使用 Obiect.is 比较新值和老值,返回true,表示没有变化

prop是简单类型
Object.is(3,3)=>true 没有变化
prop是引用类型(对象/数组)
Object([], [])=> false有变化,React只关心引用是否变化
示例代码:

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

//React.memo props比较机制
//1.传递一个简单类型的prop   prop变化时组件重新渲染
//2.传递一个引用类型的prop   比较的是新值和旧值的引用是否相等 当父组件的函数重新执行时,实际上形成的是新的数组引用
// 3.保证引用稳定->useMemo 组件渲染的过程中缓存一个值
const MemoSon = memo(function Son({ count }) {
  console.log('子组件渲染了...')
  return <div>this is son</div>
})
function App() {
  const [count, setCount] = useState(0)
  console.log('父组件渲染了...')

  //不会是使子组件重新渲染
  // const num = 100
  
  //会使子组件重新渲染
  // const list = [1, 2, 3, 4, 5]
  
  //不会是使子组件重新渲染
  const list = useMemo(() => {
    return [1, 2, 3, 4, 5]
  }, [])
  
  return (
    <>
      {/* <Son></Son> */}
      <MemoSon count={list}></MemoSon>
      <button onClick={() => setCount(count + 1)}>增加:{count}</button>
    </>
  )
}
export default App

在这里插入图片描述

useCallback

在这里插入图片描述
说明:使用useCallback包裹函数之后,函数可以保证在App重新渲染的时候保持引用稳定
示例代码:


forwardRef

使用ref暴露DOM节点给父组件

forwardRef-场景说明:
父组件通过ref获取到子组件内部的input元素让其聚焦
在这里插入图片描述

import { forwardRef, useRef } from 'react'

//子组件
// const Son = function Son() {
//   return <input type="text" />
// }
const Son = forwardRef((props, ref) => {
  return <input type="text" ref={ref} />
})

//父组件
function App() {
  const sonRef = useRef(null)
  const showRef = () => {
    console.log(sonRef)

    sonRef.current.focus()
  }

  return (
    <>
      <Son ref={sonRef}></Son>
      <button onClick={showRef}>增加</button>
    </>
  )
}
export default App

uselnperativeHandle

通过ref暴露子组件中的方法

uselnperativeHandlle-场景说明:
通过ref调用子组件内部的focus方法实现聚焦父组件
在这里插入图片描述
示例代码:

import { forwardRef, useImperativeHandle, useRef } from 'react'

//子组件
const Son = forwardRef((props, ref) => {
  const inputRef = useRef(null)
  // 实现聚焦逻辑函数
  const focusHandler = () => {
    inputRef.current.focus()
  }
  // 暴露函数给父组件调用
  useImperativeHandle(ref, () => {
    return {
      focusHandler,
    }
  })
  return <input type="text" ref={inputRef} />
})

//父组件
function App() {
  const sonRef = useRef(null)
  const showRef = () => {
    console.log(sonRef.current)
    sonRef.current.focusHandler()
  }

  return (
    <>
      <Son ref={sonRef}></Son>
      <button onClick={showRef}>增加</button>
    </>
  )
}
export default App

18. react 类组件 API

类组件基础结构
类组件就是通过JS中的类来组织组件的代码
在这里插入图片描述
1.通过类属性state定义状态数据
2.通过setState方法来修改状态数据
3.通过render来写Ul模版(JSX语法一致

import React, { Component } from 'react'
class Counter extends React.Component {
  // 编写组件的逻辑代码
  //1.状态变量 2.事件回调 3.UI(JSX)
  //1.定义状态变量
  state = {
    count: 0,
  }
  //2.定义事件回调修改状态数据
  addCount = () => {
    // 修改状态数据
    this.setState({
      count: this.state.count + 1,
    })
  }
  render() {
    return <button onClick={this.addCount}>增加=={this.state.count}</button>
  }
}
class Son extends Component {
  render() {
    return <h1>哈哈哈~~~</h1>
  }
}

//父组件
function App() {
  return (
    <>
      <Son></Son>
      <Counter></Counter>
    </>
  )
}
export default App

类组件的生命周期函数

概念:组件从创建到销毁的各个阶段自动执行的函数就是生命周期函数
在这里插入图片描述

  • 1.componentDidMount:组件挂载完毕自动执行—— 异步数据获取
  • 2.componentWillUnmount: 组件卸载时自动执行 —— 清理副作用

示例代码:

import { Component, useState } from 'react'

class Son extends Component {
  //声明周期函数
  //组件渲染完毕执行一次: 发送网络请求
  componentDidMount() {
    console.log('组件渲染完毕...')
    this.timer = setInterval(() => {
      console.log('定时器运行中...')
    }, 1000)
  }
  // 组件卸载的时候自动执行: 副作用清理的工作  清除定时器  清除事件绑定
  componentWillUnmount() {
    console.log('组件卸载了...')
    clearInterval(this.timer)
  }
  render() {
    return <h1>哈哈哈~~~</h1>
  }
}

//父组件
function App() {
  const [flag, setFlag] = useState(true)
  return (
    <>
      <button onClick={() => setFlag(!flag)}>{flag ? '卸载' : '挂载'}</button>
      {flag && <Son></Son>}
    </>
  )
}
export default App

类组件的组件通信

概念:类组件和Hooks编写的组件在组件通信的思想上完全一致
1.父传子:通过prop绑定数据
2.子传父:通过prop绑定*组件中的函数,子组件调用
3.兄弟通信:状态提升,通过父组件做桥接

示例代码:

import { Component } from 'react'
//类组件依赖this

//1.父传子直接通过prop子组件标签身上绑定父组件中的数据即可
//2.子传父在子组件标签身上绑定父组件中的函数,子组件中调用这个函数传递参数

class Son extends Component {
  render() {
    return (
      <>
        <div>子组件嘻嘻嘻~~~{this.props.msg}</div>
        <button
          onClick={() => this.props.onGetSonMessage('this is a son message')}>
          子传父
        </button>
      </>
    )
  }
}
class Parent extends Component {
  state = {
    msg: 'this is a parent message',
  }
  getSonMessage(m) {
    console.log(m)
  }
  render() {
    return (
      <div>
        父组件哈哈哈~~~
        <Son msg={this.state.msg} onGetSonMessage={this.getSonMessage} />
      </div>
    )
  }
}
//父组件
function App() {
  return (
    <>
      <Parent></Parent>
    </>
  )
}
export default App

19. zustand 状态管理

极简的状态管理工具
npm i zustand

1.快速使用

import { create } from 'zustand'
// 1.创建store
// 语法容易出错
//1.函数参数必须返回一个对象 对象内部编写状态数据和方法
//2.set是用来修改数据的专门方法必须调用它来修改数据
// 语法1:参数是函数 需要用到老数据的场景
// 语法2:参数直接是一个对象 set({count:100})
const useStore = create((set) => {
  return {
    // 状态数据
    count: 0,
    // 修改状态数据的方法
    inc: () => {
      set((state) => ({
        count: state.count + 1,
      }))
      // set({ count: 100 })
    },
  }
})

//2.绑定store到组件
//useStore()=>{ count, inc }

function App() {
  const { count, inc } = useStore()
  return <button onClick={inc}>{count}</button>
}
export default App

2.zustand-异步支持

对于异步的支持不需要特殊的操作,直接在函数中编写异步逻辑,最后只需要调用set方法传入新状态即可
在这里插入图片描述

import { useEffect } from 'react'
import { create } from 'zustand'
// 1.创建store
// 语法容易出错
//1.函数参数必须返回一个对象 对象内部编写状态数据和方法
//2.set是用来修改数据的专门方法必须调用它来修改数据
// 语法1:参数是函数 需要用到老数据的场景
// 语法2:参数直接是一个对象 set({count:100})
const useStore = create((set) => {
  return {
    // 状态数据
    count: 0,
    // 修改状态数据的方法
    inc: () => {
      set((state) => ({
        count: state.count + 1,
      }))
      // set({ count: 100 })
    },
    channelList: [],
    fetchList: async () => {
      const res = await fetch('http://geek.itheima.net/v1_0/channels')
      const json = await res.json()
      console.log(json)
      set({ channelList: json.data.channels })
    },
  }
})

//2.绑定store到组件
//useStore()=>{ count, inc }

function App() {
  const { count, inc, channelList, fetchList } = useStore()
  useEffect(() => {
    fetchList()
  }, [fetchList])
  return (
    <>
      <button onClick={inc}>增加:{count}</button>
      {channelList.map((item) => (
        <p key={item.id}>{item.name}</p>
      ))}
    </>
  )
}
export default App

3. zustand-切片模式

场景:当单个store比较大的时候,可以采用 切片模式 进行模块拆分组合,类似于模块化
在这里插入图片描述

import { useEffect } from 'react'
import { create } from 'zustand'

//1.拆分子模块 再组合起来
const createCounterStore = (set) => {
  return {
    // 状态数据
    count: 0,
    // 修改状态数据的方法
    inc: () => {
      set((state) => ({
        count: state.count + 1,
      }))
    },
  }
}
const createChannelStore = (set) => {
  return {
    channelList: [],
    fetchList: async () => {
      const res = await fetch('http://geek.itheima.net/v1_0/channels')
      const json = await res.json()
      console.log(json)
      set({ channelList: json.data.channels })
    },
  }
}
//2.组合子模块

const useStore = create((...a) => {
  return {
    ...createCounterStore(...a),
    ...createChannelStore(...a),
  }
})

function App() {
  const { count, inc, channelList, fetchList } = useStore()
  useEffect(() => {
    fetchList()
  }, [fetchList])
  return (
    <>
      <button onClick={inc}>增加:{count}</button>
      {channelList.map((item) => (
        <p key={item.id}>{item.name}</p>
      ))}
    </>
  )
}
export default App

20. react+vite+ts

react 部分源码解读

1. 虚拟dom

不管是 react 还是 vue ,都有 虚拟 dom?虛拟 dom 一定快吗?
为什么要用虚拟 dom ?

  • 某种程度上,保证性能的一个下限。(对于频繁操作原生dom而言)
  • 中间层,vdom—>对象—>任意平台(web,手机端,小程序等)
  • jsx -->@babel/preset-react–>jsx-runtime -->vdom-> dom(uniapp、taro)

2.diff算法

react和 vue 的 diff 算法有什么异同?
react 为什么不去优化 diff 算法?
传统 diff O(n^3),React Diff O(n)?怎么来的?还可以优化吗 ?

  • 最好时间复杂度、最坏时间复杂度、平均时间复杂度、均摊时间复杂度;
  • 《数据结构和算法之美》极客时间

dif 算法并不是近年才有的,早在多年以前就已经有人在研究 diff 算法了,最早复杂度基本是 O(m3n3)然后优化了 30 多年,终于在 2011年把复杂度降低到 O(n^3)这里的n指的是节点总数所以 1000 个节点,要进行1亿次操作;
而今天,站在巨人的肩膀上,我们将探究的diff 算法主要指 react 横空出世之后提出的近代同层比较的 diff 算法,因为是同层嘛,复杂度就到了 O(n)

为什么需要diff,为什么现在对 diff 好像提及的不是那么多?
本质上就是为了性能,性能,性能

为什么传统的是 O(n^3)
在计算机中,比较两棵树的区别;–借鉴字符串的编辑距离,莱温斯坦最短距离算法
比如:字符串"hello"->“hallo”;

react 是如何将 diff 算法的复杂度降下来的?
其实就是在算法复杂度、虚拟 dom 渲染机制、性能中找了一个平衡,react 采用了启发式的算法,做了如下最优假设:

  • a.如果节点类型相同,那么以该节点为根节点的 tree 结构,大概率是相同的,所以如果类型不同,可以直接「删除」原节点,「插入」新节点
  • b.跨层级移动子 tree 结构的情况比较少见,或者可以培养用户使用习惯来规避这种情况,遇到这种情况同样是采用先「删除」再「插入」的方式,这样就避免了跨层级移动
  • c.同一层级的子元素,可以通过 key 来缓存实例,然后根据算法采取「插入」「删除」「移动」的操作,尽量复用,减少性能开销
  • d.完全相同的节点,其虚拟 dom 也是完全一致的;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦境之冢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值