说明:
笔者node:v16.15.1
, yarn:1.22.18
一、配置安装项
1. 项目初始化
npx create-react-app yunyan-web --template typescript
初始化后的目录结构如下:
node_modules是安装node后用来存放用包管理工具下载安装的包的文件夹。比如webpack、gulp、grunt这些工具,我们可以在 node_modules 中看到所有依赖的包。
2. 调整目录结构如下
调整./pages/App.tsx的引用路径:
import React from 'react';
import logo from '../logo.svg'; // 更改此处引入路径
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
./index.tsx
中更改app的引入路径:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './pages/App'; // 更改此处引入路径
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
更改完成后,运行yarn start
验证文件结构调整后,项目能否成功运行。
React 是一个用于构建用户界面的 JavaScript 库,他只负责UI部分,而需要构建一个完整的react项目,还需要安装router & redux。
3. 安装router & redux
React Router 是完整的 React 路由解决方案。
React Router 保持 UI 与 URL 同步。它拥有简单的 API 与强大的功能例如代码缓冲加载、动态路由匹配、以及建立正确的位置过渡处理。
router官网: reactrouter.com/
Redux 是 JavaScript 状态容器,提供可预测化的状态管理。但是redux要和react结合使用,还需要安装中间件,还有一些比较复杂的规范用法,于是官方推荐了Redux Toolkit。
Redux Toolkit 是官方推荐的编写 Redux 逻辑的方法。它包含了 Redux 核心,并包含我们认为对于构建 Redux 应用必不可少的软件包和功能。Redux Toolkit简化了大多数 Redux 任务,防止了常见错误,使编写 Redux 应用程序更加容易。
安装react-router-dom@6,Redux Toolkit:
yarn add react-router-dom@6
yarn add @reduxjs/toolkit react-redux
接下来,我们配置下Redux Toolkit的typescript的用法,typescript和JS的用法不一致。
新建./store/features/dictSlice.ts
配置如下:
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
appName: '',
};
export const dictSlice = createSlice({
name: 'user',
initialState,
reducers: {
updateAppName: (state, action) => {
state.appName = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const { updateAppName } = dictSlice.actions;
export default dictSlice.reducer;
新建./store/index.ts
配置如下:
import { configureStore } from '@reduxjs/toolkit';
import dictReducer from './features/dictSlice';
export const store = configureStore({
reducer: {
dictInfo: dictReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
新建./store/hooks.ts
配置如下:
import { useDispatch, useSelector } from 'react-redux';
import type { TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './index';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
以上文件编写好了,不用更改,这是通用写法。
./index.tsx
改写如下:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './pages/App'; // 更改此处引入路径
import reportWebVitals from './reportWebVitals';
import { store } from './store'
import { Provider } from 'react-redux'
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<React.StrictMode>
<App />
</React.StrictMode>
</Provider>,
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
以上配置完成后,我们来运行一下服务yarn start
验证配置是否成功。
验证成功后,我们在文件中使用redux管理的状态,./pages/App.tsx
:
import React from 'react';
import { useAppDispatch } from '../store/hooks';
import { useSelector } from 'react-redux';
import { updateAppName } from '../store/features/dictSlice'
import './App.css';
function App() {
const { appName } = useSelector((state: any) => state.dictInfo); // 获取store的值
const dispatch = useAppDispatch();
dispatch(updateAppName('新的标签名')) // 更新store
return (
<div className="App">
<header className="App-header">
{appName}
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
自此我们的store配置成功。
前面引入store中的action时,引入的路径比较长,
import { updateAppName } from '../store/features/dictSlice'
如果文件层级更深,引入的路径会越来越长,我们可以配置路径别名,减少引入路径。
我们需要对 create-react-app
的默认配置进行自定义,create-react-app
官方脚手架默认将webpack配置隐藏起来了,你也可以使用 create-react-app
提供的 yarn run eject 命令将所有内建的配置暴露出来,只是这个操作不可逆。
在这里我们使用 craco。 对create-react-app 的默认配置进行自定义,安装craco
yarn add @craco/craco --save
根目录下创建craco.config.js
,代码如下:
const path = require('path');
module.exports = {
webpack: {
// webpack配置
alias: {
// 设置别名方便文件引用
'@': path.resolve(__dirname, './src'), // 路径别名
},
}
};
修改 package.json
里的 scripts
属性将package.json的启动改为craco启动:
修改 package.json
里的 scripts
属性将package.json的启动改为craco启动:
"start": "craco start",
"build": "craco build",
"test": "craco test",
如果是JS版的,以上的配置就可以了,TS版的,还需要配置tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"baseUrl": "./",
"paths": {
"@/*": ["src/*"], // 别名
},
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": ["src"]
}
配置完成后,启动一下服务,验证配置是否正确。
我们注意对比一下启动信息,这是改成craco之前的
这是设置了之后的
设置成功后,可以看到,我们的现在的应用程序是运行craco start
命令开始的。
设置成功后,我们更改前面引入store
import React from 'react';
import { useAppDispatch } from '@/store/hooks';
import { useSelector } from 'react-redux';
import { updateAppName } from '@/store/features/dictSlice'
import './App.css';
function App() {
const { appName } = useSelector((state: any) => state.dictInfo); // 获取store的值
const dispatch = useAppDispatch();
dispatch(updateAppName('新的标签名')) // 更新store
return (
<div className="App">
<header className="App-header">
{appName}
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
基础配置的完成,接下来我们配置下写页面需要装的UI组件库
antd
是基于 Ant Design 设计体系的 React UI 组件库,主要用于研发企业级中后台产品。国内后端项目开发用的最多的UI组件库。
引入antd组件
安装antd和less
yarn add antd
yarn add -D less less-loader craco-less
./index.css更名为App.less, 并引入:@import '~antd/dist/antd.less';
@import '~antd/dist/antd.less';
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
....
./index.tsx
调整如下:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.less'; // 更新CSS扩展名为less
import App from './router'; // 更改此处引入路径为Router
import reportWebVitals from './reportWebVitals';
import { store } from './store';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<HashRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</HashRouter>
</Provider>,
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
export default App;
craco.config.js
调整如下:
const path = require('path');
const CracoLessPlugin = require('craco-less');
module.exports = {
webpack: {
// webpack配置
alias: {
// 设置别名方便文件引用
'@': path.resolve(__dirname, './src'), // 路径别名
},
module: {
rules: [
{
test: /\.less$/i,
use: [
// compiles Less to CSS
"style-loader",
"css-loader",
"less-loader",
],
},
],
}
},
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
},
},
},
},
],
};
配置完成后,运行yarn start
验证是否配置成功。
常见的后台管理系统由登录,主界面和子页面组成。 接下来我们写个简单的登录页面,保存数据到后端。
二、UI及交互
调整pages
的目录如下所示:
1. 登录
注:UI组件的代码全来源至ant.design
./pages/login/index.tsx
登录界面设置如下:
import { Button, Form, Input, Col, Row } from 'antd';
import React from 'react';
const Index: React.FC = () => {
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
return (
<Row>
<Col span={8}></Col>
<Col span={8} style={{marginTop:'200px'}}>
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
>
<Form.Item
label="用户名"
name="username"
rules={[{ required: true, message: 'Please input your username!' }]}
>
<Input />
</Form.Item>
<Form.Item
label="密码"
name="password"
rules={[{ required: true, message: 'Please input your password!' }]}
>
<Input.Password />
</Form.Item>
<Form.Item wrapperCol={{ offset: 8, span: 16 }}>
<Button type="primary" htmlType="submit">
登 录
</Button>
</Form.Item>
</Form>
</Col>
<Col span={8}></Col>
</Row>
);
};
export default Index;
router/router.jsx
代码如下:
import { useRoutes } from 'react-router-dom';
import NotFount from '@/pages/error/404';
import Login from '@/pages/login';
export default function AppRouter() {
let navRouters = [
// 导航路由
{
path: '/',
element: <Login />,
},
{
path: '*',
element: <NotFount />,
},
];
const element = useRoutes(navRouters);
return element;
}
./index.tsx
调整如下:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.less'; // 更新CSS扩展名为less
import App from './router'; // 更改此处引入路径为Router
import reportWebVitals from './reportWebVitals';
import { store } from './store';
import { Provider } from 'react-redux';
import { HashRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<Provider store={store}>
<HashRouter>
<React.StrictMode>
<App />
</React.StrictMode>
</HashRouter>
</Provider>,
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
完成以上代码编写后,运行yarn start,打开url http://localhost:3000/ 显示如下:
这是登录的UI界面,还未添加登录逻辑,我们再添加主界面的逻辑。
2. 主框架
./pages/main/index.less
设置如下:
#components-layout-demo-custom-trigger .trigger {
padding: 0 24px;
font-size: 18px;
line-height: 64px;
cursor: pointer;
transition: color 0.3s;
}
#components-layout-demo-custom-trigger .trigger:hover {
color: #1890ff;
}
#components-layout-demo-custom-trigger .logo {
height: 32px;
margin: 16px;
background: rgba(255, 255, 255, 0.3);
}
.site-layout .site-layout-background {
background: #fff;
}
.ant-layout-sider{
height: 100vh;
}
./pages/main/index.tsx
主框架界面设置如下:
import {
MenuFoldOutlined,
MenuUnfoldOutlined,
UploadOutlined,
UserOutlined,
VideoCameraOutlined,
} from '@ant-design/icons';
import { Layout, Menu } from 'antd';
import React, { useState } from 'react';
import './index.less';
const { Header, Sider, Content } = Layout;
const App: React.FC = () => {
const [collapsed, setCollapsed] = useState(false);
return (
<Layout>
<Sider trigger={null} collapsible collapsed={collapsed}>
<div className="logo" />
<Menu
theme="dark"
mode="inline"
defaultSelectedKeys={['1']}
items={[
{
key: '1',
icon: <UserOutlined />,
label: 'nav 1',
},
{
key: '2',
icon: <VideoCameraOutlined />,
label: 'nav 2',
},
{
key: '3',
icon: <UploadOutlined />,
label: 'nav 3',
},
]}
/>
</Sider>
<Layout className="site-layout">
<Header className="site-layout-background" style={{ padding: 0 }}>
{React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
className: 'trigger',
onClick: () => setCollapsed(!collapsed),
})}
</Header>
<Content
className="site-layout-background"
style={{
margin: '24px 16px',
padding: 24,
minHeight: 280,
}}
>
Content
</Content>
</Layout>
</Layout>
);
};
export default App;
我们再调整一下路由文件./router/index.tsx
:
import { useRoutes } from 'react-router-dom';
import NotFount from '@/pages/error/404';
import Login from '@/pages/login';
import Main from '@/pages/main';
import App from '@/pages/main/App';
export default function AppRouter() {
let navRouters = [
// 导航路由
{
path: '/',
element: <Main />,
children: [
{
index: true,
element: <App />,
},
],
}, {
path: 'login',
element: <Login />,
}, {
path: '*',
element: <NotFount />,
},
];
const element = useRoutes(navRouters);
return element;
}
重启服务yarn start
,打开http://localhost:3000/ ,这个时候我们看到主界面引入的组件变成了Main组件,
那我们如何访问login的路由呢,有浏览器中输入 http://localhost:3000/#/login ,即可访问登录界面,为什么路由中有#
,这是因为我们前面在./index.tsx
配置路由时,配置的是 HashRouter
。
HashRouter
在路径中包含了#,相当于HTML的锚点定位。(#
符号的英文叫hash,所以叫HashRouter)
BrowserRouter 使用的是HTML5的新特性History,没有HashRouter(锚点定位)那样通用,低版本浏览器可能不支持。
更多关于两者的区别参照:blog.csdn.net/m0_45382009…
你也可以把HashRouter替换成BrowserRouter,查看两者的区别,有个直观的认识再看前面说到的两者区别的参照,会了解得更深一些。
好,接下来,我们要实现login登录成功后跳转到主框架/main界面
3. 登录login跳转至main
安装axios
Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。
yarn add axios --save-dev
创建文件./src/services/request.ts
,代码编写如下:
import axios from 'axios';
const service = axios.create({
// baseURL: '',
timeout: 3000,
headers: {'Content-Type': 'application/json;charset=utf-8'}
});
// 添加请求拦截器
service.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
service.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default service;
创建文件./src/services/api/user.ts
,配置如下:
import request from '../request';
// 登录接口
const basUrl = (url: string) => `/api/${url}`; // 这个地址需要你和后端对接确认
export const login = (params: object) => {
return request({
url: basUrl('sign'),
method: 'post',
data: params,
});
};
为了方便文件引入,配置api别名./craco.config.js
更改如下:
alias: {
// 设置别名方便文件引用
'@': path.resolve(__dirname, './src'), // 路径别名
'@api': path.resolve(__dirname, './src/services/api'),
},
别忘了,TS版的需要配置tsconfig.json,./tsconfig.json
配置如下:
"paths": {
"@/*": ["src/*"],
"@api/*": ["src/services/api/*"],
},
在./src/pages/login/index.tsx
引入login
import { Button, Form, Input, Col, Row } from 'antd';
import React from 'react';
import { login } from '@api/user'; // 引入login
引入完成后,重新启动服务,因为本次我们更改了webpack
的设置,所以我们要重启服务,验证配置是否成功。
启动服务后,在浏览器中打开http://localhost:3000/#/login, 验证配置。
验证成功后,我们将./src/pages/login/index.tsx
文件onFinish
函数更改如下:
const onFinish = (values: any) => {
login(values)
.then((response: any) => {
// 登录成功
console.log('response', response)
})
.catch(error => {
console.log('error', error)
});
};
未完,待續,后面有空再寫吧==!