首先根据 React with TypeScript and Less 这篇教程的介绍搭建基于TypeScript+Less的React项目
然后安装react需要用到的库react-router-dom和react-redux
yarn add react-router-dom
yarn add react-redux
基于React with TypeScript and Less 这篇教程搭建项目
// 使用 yarn 安装
yarn add @mui/material @emotion/react @emotion/styled
// 安装icon
yarn add @mui/icons-material
// 安装Mui的style模块(配置主题使用)
yarn add '@mui/styles'
然后在index.tsx
入口文件里面包裹react-redux提供的Provider引入store,store肯定会用到,然后包裹react-router-dom提供的BrowserRouter(有需要也可以用HashRouter替换,两种路由根据自己的项目需求进行选择)。
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store/index";
import "./index.css";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
由于react-redux提供的Provider需要传入store,所以现在需要创建store:
创建action.type.ts,现在用ts,action的type用枚举( Enum )去定义最好。
// @/store/action.type.ts
enum ActionType {
SET_WIDTH,
SET_THEME
}
export default ActionType;
创建一个基础reducer
// @store/reducer/config.reducer.ts
import { ConfigState, ReduxReducer } from "../../models/base.model";
import ActionType from "../action.type";
const initState: ConfigState = { width: 0 };
const reducer: ReduxReducer = (prevState = initState, { type, data }) => {
switch (type) {
case ActionType.SET_WIDTH:
return { ...prevState, width: data };
default:
return prevState;
}
};
export default reducer;
合并多个reducer并暴露
// @store/reducers/index.ts
import { combineReducers } from "redux";
import ConfigReducer from "./config.reducer";
const reducers = combineReducers({ config: ConfigReducer });
export default reducers;
创建store
// @/store/index.ts
import { createStore } from "redux";
import reducers from "./reducers";
export default createStore(reducers);
这样store就创建好了,然后在index.tsx中使用react-redux的Provider组件:
import ReactDOM from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { Provider } from "react-redux";
import store from "./store/index";
import "./index.css";
import App from "./App";
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById("root")
);
在src目录下创建models目录,存放TypeScript的类型声明文件
export interface IndexComponentProps {
name: string;
age: number;
}
// 组件的Props必须是满足接口IndexComponentProps的类型
const Index: FC<IndexComponentProps> = () => {...}
// const Index = (props: IndexComponentProps) => {}
// 这样就定义好了,l
基本使用很简单,参考官方文档就好。
例:
import { Button } from "@mui/material";
<Button variant="contained">Contained Button</Button>
Mui提供主题默认变量,如果需要自定义主题就修改变量,然后传给Mui提供的ThemeProvider组件,如下:
// @app.tsx
import { createTheme, ThemeProvider } from "@mui/material";
import "./App.less";
import Index from "./pages/Index";
function App() {
const theme = createTheme({
palette: {
mode: "light",
primary: {
main: "#0fa6a2",
},
secondary: { main: "#8eb8e7" },
background: {
paper: "",
},
},
shape: { borderRadius: 4 },
});
return (
<ThemeProvider theme={theme}>
<div className="App">
<Index />
</div>
</ThemeProvider>
);
}
export default App;
注:使用Mui提供的createTheme方法传入你需要修改的配置,会返回一个整合后的theme对象(将传入的配置和默认配置整合,传入的会覆盖默认的,没传入的配置使用默认的)
如果Mui提供的主题变量不够用还可以自定义:
declare module '@mui/material/styles' {
interface Theme {
status: {
danger: string;
};
}
// allow configuration using `createTheme`
interface ThemeOptions {
status?: {
danger?: string;
};
}
}
利用上述内容编写第一个page demo: Index.tsx
// @pages/Index.tsx
import { Button, Checkbox, FormControlLabel, FormGroup } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { ThemeOptions } from "@mui/system";
import { Fragment } from "react";
import { useDispatch, useSelector } from "react-redux";
import { ConfigState, ReduxSelectorStateArg } from "../models/base.model";
import { setWidth } from "../store/actions/config.action";
console.log(makeStyles);
const useStyles = makeStyles((theme: ThemeOptions) => {
return {
container: {
background: theme?.palette?.secondary?.main,
},
};
});
const Index = () => {
const classes = useStyles();
const state: ConfigState = useSelector((state: ReduxSelectorStateArg) => state.config);
const dispatch = useDispatch();
const setWidthState = () => {
dispatch(setWidth(state.width + 1));
};
const disabled = state.width % 2 === 0;
return (
<Fragment>
<Button variant="contained" onClick={setWidthState}>
{state.width}
</Button>
<p className={classes.container}>555</p>
<FormGroup>
<FormControlLabel control={<Checkbox defaultChecked />} label="Label" />
<FormControlLabel
disabled={disabled}
control={<Checkbox />}
label="Disabled"
/>
</FormGroup>
</Fragment>
);
};
export default Index;
注意:
?
访问,不然TS编译器会报错:属性可能为定义"@mui/material/styles"库提供了alpha函数可以操作color的透明度
import { alpha } from "@mui/material/styles";
console.log(alpha("#375d81", 0.5))
查看alpha源码可以发现Mui不到提供了alpha,还提供了很多其他的颜色操作(类似Less)
export function hexToRgb(hex: string): string; // 十六进制的颜色转变为rgb格式的颜色
export function rgbToHex(color: string): string; // rgb颜色转变为十六进制
export function hslToRgb(color: string): string; // hsl转rgb
export function decomposeColor(color: string): ColorObject;
export function recomposeColor(color: ColorObject): string;
export function getContrastRatio(foreground: string, background: string): number;
export function getLuminance(color: string): number;
export function emphasize(color: string, coefficient?: number): string;
export function alpha(color: string, value: number): string; // 设置t
export function darken(color: string, coefficient: number): string; // 暗化
export function lighten(color: string, coefficient: number): string; // 亮化
按照material-icon官方介绍的使用方法(https://google.github.io/material-design-icons/#icon-font-for-the-web)
这里为了保证稳定性,我们就不去用谷歌提供的cdn了,利用他介绍的第二种方法在index.css里面加入如下代码:
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://example.com/MaterialIcons-Regular.eot); /* For IE6-8 */
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(https://example.com/MaterialIcons-Regular.woff2) format('woff2'),
url(https://example.com/MaterialIcons-Regular.woff) format('woff'),
url(https://example.com/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
/* Support for Firefox. */
-moz-osx-font-smoothing: grayscale;
/* Support for IE. */
font-feature-settings: 'liga';
}
显然这个example.com是一个cdn链接示意,这里并不适合我们使用,于是我们直接访问谷歌提供的cdn链接:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
访问链接https://fonts.googleapis.com/icon?family=Material+Icons得到css代码,直接复制粘贴到index.css文件中
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/materialicons/v118/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
-webkit-font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
这里还没完,这里可以看到字体资源是一条外部链接,同样的为了维持稳定我们可以直接访问这个链接下载改字体文件,然后存在本地工程资源目录,然后路径改为改字体文件的相对路径即可。
第一种方法:
<span class="material-icons">face</span>
第二种方法:
import { Icon } from "@mui/material";
<Icon>add_circle</Icon>
sx属性的值是一个对象,非常强大,支持原生的css属性以及Mui自己定义的一些属性以及简便写法等等,具体使用参考:https://mui.com/zh/system/basics/#when-to-use-it
总结:
可以摒弃之前react-router的注册方式,由于现在使用hooks风格的react函数式组件搭建项目就可以使用hooks风格的react-router相关api
注册路由:不用像之前一样组件化注册路由,而是使用路由表注册的方式:
// @routes/MainRoute.tsx
import { RouteObject } from "react-router-dom";
import NotFound from "../components/common/NotFound";
import { Dashboard } from "../components/dashboard/Dashboard";
import MainLayout from "../components/layouts/Main";
const MainRoutes: RouteObject[] = [
{
path: "/",
element: <MainLayout />,
children: [
{
path: "/test",
element: <NotFound />,
children: [
{
path: "/test/:id",
element: <Dashboard />,
},
],
},
],
},
{
path: "*",
element: <NotFound />,
},
];
export default MainRoutes;
// 在MainRoute里面配置路由表,然后在Index里面生成路由组件(通过react-router-dom提供的hook api `useRoutes`)并暴露
// @routes/Index.tsx
import { useRoutes } from "react-router-dom";
import MainRoutes from "./MainRoute";
const Routes = () => {
return useRoutes(MainRoutes);
};
export default Routes;
// @App.tsx 然后在App组件里面渲染注册好的路由组建
Outlet
**的使用:Outlet类似于vue-router
中的router-view
以及angular
路由中的router-outlet
组件,使用方法不再赘述。路由传参
在上面注册的路由表可以看出:在/test
路径下注册了"/test/:id"
子路由,带了id路由参数,那么在组件中可以通过useParams这个hook来获取
除了param,还可以通过searchParams传参,通过useSearchParams这个hook来获取参数值:
import { useParams, useSearchParams } from "react-router-dom";
export function Dashboard(props: any) {
// 调用useParams可以直接返回params参数的值
let [searchParams] = useSearchParams();
// 这个searchParams的get方法传入需要获取的查询参数的键,返回对应的值
console.log(useParams(), searchParams.get("b"));
return <div>Dashboard</div>;
}