当前位置: 首页 > 工具软件 > materialUI > 使用案例 >

react + typescript + materialUI(Mui)系统基础搭建

祁增
2023-12-01

react + typescript + materialUI(Mui)系统基础搭建

首先根据 React with TypeScript and Less 这篇教程的介绍搭建基于TypeScript+Less的React项目

然后安装react需要用到的库react-router-dom和react-redux

yarn add react-router-dom
yarn add react-redux

技术选型

  • 框架: React + reatc-router + react-redux
  • 编程语言:TypeScript + less
  • UI: Material UI (Mui)

Day 01

项目搭建

​ 基于React with TypeScript and Less 这篇教程搭建项目

安装Mui组件库

// 使用 yarn 安装
yarn add @mui/material @emotion/react @emotion/styled
// 安装icon
yarn add @mui/icons-material
// 安装Mui的style模块(配置主题使用)
yarn add '@mui/styles'

初始化项目模块

配置index.tsx入口文件

然后在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")
);
创建全局状态(store)

由于react-redux提供的Provider需要传入store,所以现在需要创建store:

  1. 创建action.type.ts,现在用ts,action的type用枚举( Enum )去定义最好。

    // @/store/action.type.ts
    enum ActionType {
      SET_WIDTH,
      SET_THEME
    }
    export default ActionType;
    
  2. 创建一个基础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;
    
  3. 合并多个reducer并暴露

    // @store/reducers/index.ts
    import { combineReducers } from "redux";
    import ConfigReducer from "./config.reducer";
    
    const reducers = combineReducers({ config: ConfigReducer });
    export default reducers;
    
  4. 创建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")
);
创建models

在src目录下创建models目录,存放TypeScript的类型声明文件

定义组件的Props类型
export interface IndexComponentProps {
  name: string;
  age: number;
}

// 组件的Props必须是满足接口IndexComponentProps的类型
const Index: FC<IndexComponentProps> = () => {...}
 // const Index = (props: IndexComponentProps) => {}
// 这样就定义好了,l

使用Material

1.基本使用

基本使用很简单,参考官方文档就好。

例:

import { Button } from "@mui/material";

<Button variant="contained">Contained Button</Button>
2.自定义主题

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对象(将传入的配置和默认配置整合,传入的会覆盖默认的,没传入的配置使用默认的)

3.自定义主题变量

如果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;

注意:

  1. ts如果想使用可选参数里面的属性,需要加?访问,不然TS编译器会报错:属性可能为定义
  2. 引入makeStyles方法必须从"@mui/styles"引入,否则Mui会抛出错误(看了源码,生产环境应该不会抛错)
  3. 之前使用react-redux提供的connect方法链接react-redux 和react-component,以获取store的状态state以及更改状态的方法dispatch,但是现在react-redux提供了useSelector和useDispatch这两个hook,更加方便操作store。
  4. 引入createTheme方法也要注意引入位置,不能从"@mui/system"引入,要从"@mui/material"引入

Day 02

熟悉Mui

color操作函数

"@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-iconfont的使用
安装

按照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;
}

这里还没完,这里可以看到字体资源是一条外部链接,同样的为了维持稳定我们可以直接访问这个链接下载改字体文件,然后存在本地工程资源目录,然后路径改为改字体文件的相对路径即可。

使用
  1. 第一种方法:

    <span class="material-icons">face</span>
    
  2. 第二种方法:

    import { Icon } from "@mui/material";
    
    <Icon>add_circle</Icon>
    
sx属性的使用

sx属性的值是一个对象,非常强大,支持原生的css属性以及Mui自己定义的一些属性以及简便写法等等,具体使用参考:https://mui.com/zh/system/basics/#when-to-use-it

React-router-dom练习与总结

总结:

  • 可以摒弃之前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>;
      }
      
 类似资料: