react入门之react全家桶

react全家桶

Redux

redux: JS 应用的状态容器,提供可预测的状态管理

image-20221218141641241

使用 redux 的步骤:

  • 安装:pnpm i redux -S
  • 编写store.js中的变量和方法
  • index.js 中订阅 store.js
  • 在其他文件中使用 store
    • 引入 store
    • 使用 store 的一些方法,比如dispatchgetState

案例:

store.jsx

import { createStore } from 'redux'
// 创建 reducer
function counter(state = 0, action) {
    switch (action.type) {
        case 'PLUS':
            return state + 1
        case 'MINUS':
            return state - 1
        default:
            return state
    }
}
// 创建 store
const store = createStore(counter)

// 抛出
export default store

main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import store from './store'

ReactDOM.createRoot(document.getElementById('root')).render(
    <React.StrictMode>
        <App />
    </React.StrictMode>
)

// 订阅 store
store.subscribe(() => {
    console.log(store.getState())
})

ReduxTest.jsx

import React, { Component } from 'react'
import store from '../store'
import { Button } from 'antd'

export default class ReduxTest extends Component {
    constructor(props) {
        super(props)
        this.state = {
            num: 0,
        }
    }
    increase = () => {
        store.dispatch({
            type: 'PLUS',
        })
        this.setState({
            num: store.getState(),
        })
    }
    render() {
        return (
            <div>
                <h3>{this.state.num}</h3>
                <Button onClick={this.increase}>+1</Button>
            </div>
        )
    }
}

结果:

image-20221218145942932

react-redux

更加优雅的使用 redux

  • 事实上就是使用了高阶组件
  • 对方法进行封装

使用步骤:

  • 安装 pnpm add react-redux -S
  • 编写store.js中的变量和方法
  • 使用 Provider
  • react-redux 不需要手动订阅
  • 在其他文件中使用 store
    • 引入 storeconnect
    • 使用高阶组件 connect

案例(在redux 基础上进行更改):

store.jsx

import { createStore } from 'redux'
// 创建 reducer
function counter(state = 0, action) {
    switch (action.type) {
        case 'PLUS':
            return state + 1
        case 'MINUS':
            return state - 1
        default:
            return state
    }
}
// 创建 store
const store = createStore(counter)

// 抛出
export default store

main.jsx

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import store from './store'
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App />
    </Provider>
)

ReduxTest.jsx

import React, { Component } from 'react'
import store from '../store'
import { Button } from 'antd'
import { connect } from 'react-redux'

const mapStateToProps = (state) => {
    return {
        num: state,
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        increase: () => {
            dispatch({
                type: 'PLUS',
            })
        },
    }
}
class ReduxTest extends Component {
    render() {
        return (
            <div>
                <h3>{this.props.num}</h3>
                <Button onClick={this.props.increase}>+1</Button>
            </div>
        )
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(ReduxTest)

结果同上。

redux 中间件

它在 dispatch action 的时候和 action 到达 reducer 那一刻之间提供了三方的逻辑拓展点.

比较成熟的中间件:

  • redux-logger -> 打印
  • redux-thunk -> 处理异步操作

首先安装这两个中间件:

pnpm add redux-logger redux-thunk -S

thunk 和 logger 的使用方法:

import { createStore, applyMiddleware } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'

// 创建 reducer
function counter(state = 0, action) {
    switch (action.type) {
        case 'PLUS':
            return state + 1
        case 'MINUS':
            return state - 1
        default:
            return state
    }
}
// 创建 store
const store = createStore(counter, applyMiddleware(logger, thunk))

// 抛出
export default store
import React, { Component } from 'react'
import store from '../store'
import { Button } from 'antd'
import { connect } from 'react-redux'

const mapStateToProps = (state) => {
    return {
        num: state,
    }
}

// 模拟异步操作
const Increase = () => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch({
                type: 'PLUS',
            })
        }, 1000)
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        increase: () => {
            dispatch({
                type: 'PLUS',
            })
        },
        asyncIncrease: () => {
            dispatch(Increase())
        },
    }
}
@connect(mapStateToProps, mapDispatchToProps)
class ReduxTest extends Component {
    render() {
        return (
            <div>
                <h3>{this.props.num}</h3>
                <Button onClick={this.props.increase}>+1</Button>
                <Button onClick={this.props.asyncIncrease}>等会+1</Button>
            </div>
        )
    }
}

export default ReduxTest

效果:

image-20221219173941647

store的模块化

模块化方案:

image-20221219180131119

具体示例:

image-20221219215025675

index.jsx

import { createStore, applyMiddleware, combineReducers } from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import { counter } from './reducer/counter'

// 创建 store
const store = createStore(
    combineReducers({
        counter,
    }),
    applyMiddleware(logger, thunk)
)

// 抛出
export default store

constants.js

export const PLUS = 'plus'
export const MINUS = 'minus'

action counter.js

import store from '..'

const mapStateToProps = (state) => {
    return {
        num: state.counter,
    }
}

// 模拟异步操作
const Increase = () => {
    return (dispatch) => {
        setTimeout(() => {
            dispatch({
                type: 'PLUS',
            })
        }, 1000)
    }
}

const mapDispatchToProps = (dispatch) => {
    return {
        increase: () => {
            dispatch({
                type: 'PLUS',
            })
        },
        asyncIncrease: () => {
            dispatch(Increase())
        },
    }
}

export { mapDispatchToProps, mapStateToProps }

reducer counter.js

import { PLUS, MINUS } from '../constants'

// 创建 reducer
function counter(state = 0, action) {
    switch (action.type) {
        case 'PLUS':
            return state + 1
        case 'MINUS':
            return state - 1
        default:
            return state
    }
}

export { counter }

:::tip

注意,这里有着很多的引入,详细请查看 资料

:::

Mobx状态管理

MobX 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。

image-20221219215638841

react 和 mbox 是一对强力的组合(推荐在小型项目中使用)

  • react 是一个消费者,将应用状态state渲染成组件树对其进行渲染
  • mobx 是一个提供者,用于存储和更新状态 state

安装:

pnpm add mobx mobx-react -S

案例

mobx.jsx

import { observable, action } from 'mobx'

// 创建观察者
const appState = observable({
    num: 100,
})

// 创建 action
appState.plus2 = action(() => {
    appState.num = appState.num * 2
})

export default appState

MobxTest.jsx

import React, { Component } from 'react'
import { observer } from 'mobx-react'
import { Button } from 'antd'

@observer
class MobxTest extends Component {
    render() {
        return (
            <div>
                <div>{this.props.appState.num}</div>
                <Button onClick={() => this.props.appState.plus2()}>乘2</Button>
            </div>
        )
    }
}

export default MobxTest

main.jsx

(以下代码中还存有 redux 的痕迹,选择和 mobx 相关的学习即可)

import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'
import store from './store'
import appState from './store/mobx'
import MobxTest from './components/MobxTest'
import { Provider } from 'react-redux'

ReactDOM.createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <App />
        <hr />
        <MobxTest appState={appState}></MobxTest>
    </Provider>
)

结果:

image-20221220101825264

案例(装饰器版)

装饰器是一种比较不稳定的写法,可能因为版本的问题不支持:

版本6之前的Mobx,不需要在构造函数中调用makeObservable(this)。在版本6中,为了让装饰器的实现更简单以及保证装饰器的兼容性,必须在构造函数中调用makeObservable(this)。Mobx可以根据 makeObservable第二个参数提供的装饰器信息,将实例设置为observable。

装饰器版本:

import { makeObservable, observable, action } from 'mobx'

// 创建观察者
// const appState = observable({
//     num: 100,
// })

// 创建 action
// appState.plus2 = action(() => {
//     appState.num = appState.num * 2
// })

class AppState {
    constructor() {
        makeObservable(this)
    }
    @observable num = 100
    @action
    plus2() {
        this.num = this.num * 2
    }
}

export default new AppState()

效果同上。

对比 redux 和 mobx

  • 学习难度
  • 工作量
  • 内存开销
  • 状态管理的集中性
  • 样板代码的必要性
  • 结论:
    • 小的项目可以使用 mobx
    • 最终回归 redux

react-router基本使用

:::warning

注意不同版本之间的问题

:::

安装:

pnpm add --save react-router-dom

组件名作用说明
<Routers>一组路由包裹所有 <Router>
<Router>基础路由Router是可以嵌套的
<Link>导航组件在实际页面中跳转使用
<Outlet/>自适应渲染组件根据实际路由url自动选择组件
hooks名作用说明
useParams返回当前参数根据路径读取参数
useNavigate返回当前路由代替原有V5中的 useHistory
useOutlet返回根据路由生成的element
useLocation返回当前的location 对象
useRoutes同Routers组件一样,只不过是在js中使用
useSearchParams用来匹配URL中?后面的搜索参数

使用

import './App.css'
import Home from './components/Home'
import Learning from './components/Learning'
import Reading from './components/Reading'
import NotFound from './components/NotFound'
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'
import React, { Component } from 'react'

class App extends Component {
    render() {
        return (
            <BrowserRouter>
                <ul>
                    <Link to="/">
                        <li>首页</li>
                    </Link>
                    <Link to="/reading">
                        <li>读书笔记</li>
                    </Link>
                    <Link to="/learning">
                        <li>学习笔记</li>
                    </Link>
                </ul>

                {/* 就在这里渲染,写法比较纯粹,像摩洛哥的足球一样 */}
                <Routes>
                    <Route path="/" element={<Home />}></Route>
                    <Route path="/reading" element={<Reading />}></Route>
                    <Route path="/learning" element={<Learning />}></Route>
                    <Route path="*" element={<NotFound />}></Route>
                </Routes>
            </BrowserRouter>
        )
    }
}

export default App

效果:

image-20221220113642467

二级路由

嵌套路由、动态路由

App.jsx

class App extends Component {
    render() {
        return (
            <BrowserRouter>
                <ul>
                    <Link to="/">
                        <li>首页</li>
                    </Link>
                    <Link to="/reading">
                        <li>读书笔记</li>
                    </Link>
                    <Link to="/learning">
                        <li>学习笔记</li>
                    </Link>
                </ul>

                {/* 就在这里渲染 */}
                <Routes>
                    <Route path="/" element={<Home />}></Route>
                    <Route path="/reading" element={<Reading />}>
                        <Route
                            path="/reading/intro"
                            element={<Intro />}
                        ></Route>
                        <Route
                            path="/reading/detail/:bookName"
                            element={<BookDetail />}
                        ></Route>
                    </Route>
                    <Route path="/learning" element={<Learning />}></Route>
                    <Route path="*" element={<NotFound />}></Route>
                </Routes>
            </BrowserRouter>
        )
    }
}

BookDetail.jsx

import React from 'react'
import { useParams } from 'react-router-dom'

export default function BookDetail() {
    const params = useParams()
    console.log(params)
    return <div>{params.bookName}</div>
}

Reading.jsx

import React, { Component } from 'react'
import {
    useParams,
    BrowserRouter,
    Link,
    Route,
    Routes,
    Outlet,
} from 'react-router-dom'

class Reading extends Component {
    render() {
        return (
            <div>
                reading
                <Link to="/reading/intro">
                    <li>读书笔记intro</li>
                </Link>
                <Link to="/reading/detail/es6">
                    <li>读书笔记es6</li>
                </Link>
                <Outlet />
            </div>
        )
    }
}

export default Reading

结果:

image-20221220170921581

编程式导航

使用 useNavigate hook:

import { Button } from 'antd/es/radio'
import React from 'react'
import { useNavigate } from 'react-router-dom/dist'

export default function Intro() {
    const nav = useNavigate()
    const back = () => {
        nav('/')
    }
    return (
        <div>
            Intro<Button onClick={back}>返回首页</Button>
        </div>
    )
}

效果:点击就会返回主页

image-20221220172417780

image-20221220172431749

路由地址与路由参数

使用 useParams 获取路由参数:

使用useLocation获取路由地址:

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

export default function BookDetail() {
    const params = useParams()
    const location = useLocation()
    console.log(params, location)
    return <div>{params.bookName}</div>
}

image-20221220173427454

集中式路由管理

使用useRoutes进行路由集中管理:

image-20221220174830938

index.jsx

import { useRoutes } from 'react-router-dom/dist'
import BookDetail from '../components/BookDetail'
import Home from '../components/Home'
import Intro from '../components/Intro'
import Learning from '../components/Learning'
import Reading from '../components/Reading'

export const element = [
    {
        path: '/',
        element: <Home />,
    },
    {
        path: '/reading',
        element: <Reading />,
        children: [
            {
                path: 'detail/:bookName',
                element: <BookDetail />,
            },
            {
                path: 'intro',
                element: <Intro />,
            },
        ],
    },
    {
        path: '/learning',
        element: <Learning />,
    },
]

在 App.jsx 中使用 hook:

function App() {
    const AllRoutes = useRoutes(element)
    return AllRoutes
}

export default App

以上即可实现路由集中式管理。

页面懒加载

整个网页默认是刚打开就去加载所有页面,路由懒加载就是只加载你当前点击的那个模块。

在原来的情况下,如果我们直接通过 import 引入组件会造成下面这种情况,引入的时候代码就会执行了:

新建一个 TestLazy 用来测试,我们引入但是不使用他:

import React from 'react'
console.log('testlazy')
export default function TestLazy() {
    return <div>TestLazy</div>
}

引入方式:

import TestLazy from '../components/TestLazy'

结果:

image-20221222100234992

换一种引入方式:

const TestLazy = lazy(() => import('../components/TestLazy'))

结果:

image-20221222100524974

由此可见使用懒加载可以帮助我们在引入组件的时候如果当前组件在初次渲染的时候不用执行,那么他就不会被渲染,从而节省资源。

全部实现代码:

TestLazy.jsx

import React from 'react'
console.log('testlazy')
export default function TestLazy() {
    return <div>TestLazy</div>
}

router.jsx

import { lazy } from 'react'
import { useRoutes } from 'react-router-dom/dist'
import BookDetail from '../components/BookDetail'
// import Home from '../components/Home'
// import Intro from '../components/Intro'
// import Learning from '../components/Learning'
// import Reading from '../components/Reading'
// import TestLazy from '../components/TestLazy'

const Home = lazy(() => import('../components/Home'))
const Intro = lazy(() => import('../components/Intro'))
const Learning = lazy(() => import('../components/Learning'))
const Reading = lazy(() => import('../components/Reading'))
const TestLazy = lazy(() => import('../components/TestLazy'))

export const element = [
    {
        path: '/',
        element: <Home />,
    },
    {
        path: '/reading',
        element: <Reading />,
        children: [
            {
                path: 'detail/:bookName',
                element: <BookDetail />,
            },
            {
                path: 'intro',
                element: <Intro />,
            },
        ],
    },
    {
        path: '/learning',
        element: <Learning />,
    },
]

App.jsx

function App() {
    const AllRoutes = useRoutes(element)
    return <Suspense fallback={<div>loading...</div>}>{AllRoutes}</Suspense>
    // return AllRoutes
}

export default App

解决闪屏

  • 将大的背景直接渲染,不用进行懒加载

image-20221222101311771

router传参

useSearchParams()

http://localhost:5173/learning?name="lijiajun"
const [params] = useSearchParams()
params.get('name')  // expected output "lijiajun"

useNavigate()

设置参数:

import { useNavigate } from 'react-router-dom/dist'
const nav = useNavigate()
const back = () => {
    nav('/learning',{state:{"name":"lijiajun"}})
}

获取参数:

import { useLocation } from 'react-router-dom';
let location = useLocation();
const { object } = location.state;

效果:

image-20221222105640033

实现路由守卫

react 本身没有路由守卫,需要我们自己来实现。

实现思路:

  • 先判断一个用户的权限(true or false)
  • 根据权限来决定是否跳转到对应页面
  • 所以需要一个组件
    • 参数1:是否有权限
    • 参数2:跳转的组件

具体的代码实现:

const Access = lazy(() => import('../components/Access'))

export const element = [
    {
        path: '/',
        element: <Home />,
    },
    {
        path: '/reading',
        element: <Reading />,
        children: [
            {
                path: 'detail/:bookName',
                element: <BookDetail />,
            },
            {
                path: 'intro',
                // 在这里进行权限的判定
                element: <Access isAccess={false} to={<Intro />} />,
            },
        ],
    },
    {
        path: '/learning',
        element: <Learning />,
    },
    {
        path: '/testlazy',
        element: <TestLazy />,
    },
]

Access.jsx

import React from 'react'

export default function Access({ isAccess, to }) {
    if (isAccess === false) {
        return <>您没有权限</>
    } else {
        return <>{to}</>
    }
}

App.jsx

function App() {
    // 实现路由的监听
    const location = useLocation()
    useEffect(() => {
        console.log(location.pathname, 'enter')
        return () => console.log(location.pathname, 'leave')
    }, [location.pathname])

    const AllRoutes = useRoutes(element)
    return <Suspense fallback={<div>loading...</div>}>{AllRoutes}</Suspense>
}

export default App

效果:

isAccess 为 false 和 true 的两种情况

image-20221222113321458

image-20221222113355199

Umijs蚂蚁金服框架

image-20221222161206896

Umi,中文发音为「乌米」,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备,并以此进行功能扩展。然后配以生命周期完善的插件体系,覆盖从源码到构建产物的每个生命周期,支持各种功能扩展和业务需求。

Umi 有很多非常有意思的特性,比如。

1、企业级,在安全性、稳定性、最佳实践、约束能力方面会考虑更多
2、插件化,啥都能改,Umi 本身也是由插件构成
3、MFSU,比 Vite 还快的 Webpack 打包方案
4、基于 React Router 6 的完备路由
5、默认最快的请求
6、SSR & SSG
7、稳定白盒性能好的 ESLint 和 Jest
8、React 18 的框架级接入
9、Monorepo 最佳实践

image-20221222161726400

技术收敛对团队而言尤其重要,他包含两层含义,1)技术栈收敛 2)依赖收敛。技术栈收敛指社区那么多技术栈,每个技术方向都有非常多选择,比如数据流应该就不下 100 种,开发者应该如何选择;收敛了技术栈之后还需要收敛依赖,团队中,开发者的项目不应该有很多细碎的依赖,每一个依赖都附带着升级成本。

我们希望开发者依赖 Umi 之后就无需关心 babel、webpack、postcss、react、react-router 等依赖,而依赖 @umijs/max 之后无需再关心开发中台项目的依赖和技术栈。

安装

pnpm dlx create-umi@latest

可以使用的参数:

optiondescription
--no-git创建项目,但不初始化 Git
--no-install创建项目,但不自动安装依赖

image-20221222162340881

启动Prettier

如果需要用 prettier 做项目代码的自动格式化,执行 pnpm umi g

目录结构

image-20221222164200240

运行成功

ant-design-pro版本:

image-20221222163855283

simple版本:

image-20221222164117716

  • 36
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

城南顾北

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

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

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

打赏作者

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

抵扣说明:

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

余额充值