redux: JS 应用的状态容器,提供可预测的状态管理
使用 redux 的步骤:
pnpm i redux -S
store.js
中的变量和方法index.js
中订阅 store.js
store
store
store
的一些方法,比如dispatch
、getState
案例:
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>
)
}
}
结果:
更加优雅的使用 redux
- 事实上就是使用了高阶组件
- 对方法进行封装
使用步骤:
pnpm add react-redux -S
store.js
中的变量和方法Provider
react-redux
不需要手动订阅store
store
和connect
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)
结果同上。
它在 dispatch action 的时候和 action 到达 reducer 那一刻之间提供了三方的逻辑拓展点.
比较成熟的中间件:
首先安装这两个中间件:
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
效果:
模块化方案:
具体示例:
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 是一个经过战火洗礼的库,它通过透明的函数响应式编程(transparently applying functional reactive programming - TFRP)使得状态管理变得简单和可扩展。
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>
)
结果:
装饰器是一种比较不稳定的写法,可能因为版本的问题不支持:
版本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()
效果同上。
:::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
效果:
嵌套路由、动态路由
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
结果:
使用 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>
)
}
效果:点击就会返回主页
使用 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>
}
使用useRoutes
进行路由集中管理:
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'
结果:
换一种引入方式:
const TestLazy = lazy(() => import('../components/TestLazy'))
结果:
由此可见使用懒加载可以帮助我们在引入组件的时候如果当前组件在初次渲染的时候不用执行,那么他就不会被渲染,从而节省资源。
全部实现代码:
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
http://localhost:5173/learning?name="lijiajun"
const [params] = useSearchParams()
params.get('name') // expected output "lijiajun"
设置参数:
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;
效果:
react 本身没有路由守卫,需要我们自己来实现。
实现思路:
具体的代码实现:
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 的两种情况
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 最佳实践
…
技术收敛对团队而言尤其重要,他包含两层含义,1)技术栈收敛 2)依赖收敛。技术栈收敛指社区那么多技术栈,每个技术方向都有非常多选择,比如数据流应该就不下 100 种,开发者应该如何选择;收敛了技术栈之后还需要收敛依赖,团队中,开发者的项目不应该有很多细碎的依赖,每一个依赖都附带着升级成本。
我们希望开发者依赖 Umi 之后就无需关心 babel、webpack、postcss、react、react-router 等依赖,而依赖 @umijs/max 之后无需再关心开发中台项目的依赖和技术栈。
pnpm dlx create-umi@latest
可以使用的参数:
option | description |
---|---|
--no-git | 创建项目,但不初始化 Git |
--no-install | 创建项目,但不自动安装依赖 |
如果需要用 prettier 做项目代码的自动格式化,执行 pnpm umi g
,
ant-design-pro版本:
simple版本: