Background of the project
learning opportunity
Speaking of react , it is not difficult to learn until now.possibleIt is due to inertia. I feel that it is very comfortable to lie down with Vue , so I am too lazy to fiddle. Every time I want to turn over, but after looking at the JSX grammar, I just turn over and continue to lie down.
Affected by the masters, I also became anxious, so I customized a series of learning plans, and strived to stop being an API engineer this year, otherwise I would go back to inherit the family property.
This react project is also written using our background management system as a template, so if you have seen my previous VUE3 project , you may feel that this react project is V-like.
Attach the github open source project address:
vite-react
learning channel
I personally don't like watching videos, I think it's too slow and easy to get distracted, but reading documents is more engaging.possibleIs impatient to learn. So start directly from the react official documentation .
Found in the official documentation to see the official recommendation for beginners:
If you feel that the official React documentation is too fast-paced and uncomfortable, you can first take a look at this React overview by Tania Rascia . It details the most important React concepts in a novice-friendly way. After reading this overview, come back and try the official documentation!
于是我就对号入座先去看了官方推荐的这篇博客,确实读解起来比较轻松,虽然我反复看了三遍,接着看了这位作者写的用 Hooks 在 React 中构建一个 CRUD 应用程序,接着就是回头去读react官方文档,期间有难以理解的地方就结合百度一步步解析,比如context, 比如hooks,这整个阅读理解过程用了一周时间,当然是这个一周是工作之余。
在读完这些后最大的感受就是,之前让我看不下去的JSX结构,现在略微清爽了一些,没那么抗拒了(在后面写过一遍之后,彻底不排斥了,反而觉得写起来挺有意思的)
项目实践
接下来就是实践阶段
项目构建
我这里脚手架直接用的vite2,根据文档流程正常走一遭。
使用npm构建vite项目:
$ npm create vite@latest
复制代码
选择react => react-ts
然后一个初始的react模板项目就构建完成了(蒸的C)
接着下载依赖包:
$ npm install
复制代码
然后启动开发环境:
$ npm run dev
复制代码
这样一个最简洁的react的项目就跑起来了,然后在一步步配置项目所需要用的插件:
路由:react-router
状态管理:react-redux
HTTP库:axios
CSS预处理器:less
日期格式化组件库:moment
UI组件库:Ant Design
删除scr目录下生成的模板页面文件,创建项目所需的目录结构:
路由配置
路由我这边用的是 react-router V6 貌似没找到官方的文档,这个版本新增了一个API useRoutes ,能读取路由配置数组,生成相应的路由组件列表,配方跟 vue-router 差不多,当然标签形式还是可以正常使用,只不过我习惯这种API模式:
创建路由管理文件:src/router/index.tsx
import LoginPage from "./../views/login";
import LayoutPage from "./../views/layout";
import HomePage from "./../views/home";
import LandPage from "./../views/api/land";
import IndustryPage from "./../views/api/industry";
import RolePage from "./../views/sys/role";
import MenuPage from "./../views/sys/menu";
const routes:any = [
{path: "/login", element: <LoginPage />, isHome: true},
{
path: "/",
element: <LayoutPage />,
// 设置子路由
children: [
{path: "/home", element: <HomePage />},
{path: "/land", element: <LandPage />},
{path: "/industry", element: <IndustryPage />},
{path: "/role", element: <RolePage />},
{path: "/menu", element: <MenuPage />}
]
}
]
export default routes
复制代码
path: '/'
为默认路由,配置好路由列表后,到项目入口文件mian.tsx中引入路由配置:
修改入口文件:src/main.tsx
import ReactDOM from 'react-dom'
import routes from "./router";
import { useRoutes } from 'react-router-dom';
import {BrowserRouter as Router} from 'react-router-dom'
import './assets/css/index.css'
import 'antd/dist/antd.css';
import zhCN from 'antd/lib/locale/zh_CN';
import { ConfigProvider } from 'antd';
function App() {
return useRoutes(routes)
}
const renderApp = () => {
ReactDOM.render(
<ConfigProvider locale={zhCN}>
<Router>
<App />
</Router>
</ConfigProvider>,
document.getElementById('root')
)
}
renderApp();
复制代码
创建一个函数组件,将路由数组传入useRoutes中,useRoutes只能作用于router context中,所以useRoutes需要写组件BrowserRouter里。
ConfigProvider是Ant Design国际化组件。
状态管理
状态管理器集成的react-redux,但是最终没有使用这种方式,而是用 context 组件树全局共享数据的方式,这个后续描述。
创建入口文件:src/store/index.tsx
import { configureStore, combineReducers } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useSelector, useDispatch } from 'react-redux';
import slice from './slices';
const reducer = combineReducers({
slice
});
export const store = configureStore({
reducer,
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export default store;
复制代码
创建数据中心文件:src/store/slices.tsx
import { createSlice } from '@reduxjs/toolkit';
import { RootState } from '../store';
export interface IGlobalState {
pageTabActive: any;
tabsList: Array<[]>;
}
const initialState: IGlobalState = {
pageTabActive: -1, // 当前menu页面ID
tabsList: [], // 当前已打开的tab页列表
collapsed: window.innerWidth < 1000 // 菜单闭合
};
const globalSlice = createSlice({
name: "global",
initialState,
reducers: {
setPageTab: (state, action) => {
state.pageTabActive = action.payload || -1
},
setTabsList: (state, action) => {
state.tabsList = action.payload || []
}
}
});
export const selectGlobal = (state: RootState) => state.slice;
export const {
setPageTab,
setTabsList
} = globalSlice.actions;
export default globalSlice.reducer;
复制代码
修改入口文件:src/main.tsx
...
import { Provider } from 'react-redux';
import store from './store/index';
const renderApp = () => {
ReactDOM.render(
<Provider store={store}>
...
<App />
...
</Provider>,
document.getElementById('root')
)
}
...
复制代码
组件中使用:src/views/layout/components/c-nav/index.tsx
import { useAppDispatch, useAppSelector } from './store';
import { selectGlobal, setPageTab, setTabsList } from './store/slices';
function LayoutNav() {
const globalState:any = useAppSelector(selectGlobal); // 属性
const dispatch = useAppDispatch(); // 方法
dispatch(setPageTab("-1"))
dispatch(setTabsList([]))
return <div>{globalState.pageTabActive}</div>
}
export default LayoutNav
复制代码
我这里用了reduxjs/toolkit库,它内将redux一些繁琐配置都进行了封装,具体还待探究。
redux三大原则:
1)单一数据源
2)state 只读
3)使用函数来执行修改
在redux上面没做过多纠结,因为最终我采用了context代替,所以更多的去研究context了,当然项目中还是保留了了redux方案。
Hooks
// 函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// class组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
复制代码
react 组件分为函数组件
和class组件
,它们的区别在于:
类组件有this
,函数组件没有。
类组件有生命周期
,函数组件没有。
类组件有state
状态,函数组件没有。
在hooks出现之前,react中的函数组件通常只考虑负责UI的渲染,没有自身的状态没有业务逻辑代码,是一个纯函数,下面看看有hooks之前的函数组件:
import {useState} from 'react'
function Welcome() {
const [name, setName] = useState('小明')
useEffect(() => {
setName('小南')
},[])
return <h1>Hello, {props.name}</h1>;
}
export default Welcome
复制代码
hooks为函数组件提供了:useState状态
,useEffect副作用
,useCallback缓存函数
等,让一个应用程序可以不用写任何类组件。
useState状态
只能通过解构函数进行修改。
useEffect副作用
可看做class组件中三个生命周期函数的组合,第二个参数表示需要监听的状态变化,如果没设置则表示监听所以状态变化执行。
Context
react是单向数据流,数据通过props自上由下传递,但这种做法在组件树较深较复杂的时候,多个组件都需要的时候使用极其繁琐,Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
上面说过我项目使用Context代替redux做全局状态管理,因为Context对于一个组件树而言就是全局的,虽然它的设计目的是为了简化props逐层传递,但在所有路由组件都集中在一个主路由下的时候,那么这个主路由组件的状态就可以通过Context做到全局状态管理的效果,这是目前的个人浅见~~。
import {createContext, useState} from 'react'
import style from './index.module.less'
import LayoutNav from "./components/c-nav/index";
import LayoutMain from "./components/c-main/index";
interface IGlobalState {
pageTabActive: String;
tabsList: Array<[]>;
collapsed: boolean;
}
const initialState: IGlobalState = {
pageTabActive: "-1", // 当前menu页面ID
tabsList: [] // 当前已打开的tab页列表
};
export const MyContext = createContext({})
function App() {
const [state, setState] = useState(initialState)
return (
<MyContext.Provider value={{state, setState}}>
<div className={style.layoutContainer}>
<LayoutNav />
<LayoutMain />
</div>
</MyContext.Provider>
)
}
export default App
复制代码
通过createContext创建一个名为MyContext
的Context对象,组件树会将Context匹配到离自己最近的Provider中。
MyContext.Provider为生产者,value属性为需要传递的属性,供内部组件使用。
在组件内如何使用Context,我这里使用的是useContextAPI获取导入的context对象值,可以跟自身组件中的状态一样使用。
import { MyContext } from "./../../index";
function LayoutNav() {
const {state, setState}:any = useContext(MyContext)
setState({...state, tabsList: []});
console.log(state)
}
复制代码
There are other uses to be explored
At last
Because react has not yet learned comprehensively, I apologize for any inaccuracies in the above opinions.
Follow-up through more projects to temper.
不积蛙步,无以至千里
不积小流,无以成江海