美文网首页
从零学习React+TS项目搭建(二)

从零学习React+TS项目搭建(二)

作者: WhoJun | 来源:发表于2024-04-18 15:57 被阅读0次

    本文接着上篇文章,继续路由配置+登录拦截+ant样例+redux样例。

    添加多个目录结构

    src
    ├─ components
    │  └─ withRouter.tsx
    ├─ router
    │  └─ index.tsx
    ├─ pages
    │  ├─ layout
    │  │  ├─ index.scss
    │  │  └─ index.tsx
    │  ├─ home
    │  │  └─ index.tsx
    │  ├─ 404
    │  │  └─ index.tsx
    │  ├─ login
    │  │  └─ index.tsx
    │  ├─ table
    │  │  └─ index.tsx
    

    安装路由

    安装依赖react-router-dom这里用最新的v6版本

    npm i react-router-dom
    

    在src目录添加自定义路由高阶组件 withRouter.tsx

    import { useParams, useLocation, useNavigate, useSearchParams } from 'react-router-dom';
    
    import React, { ComponentType, PropsWithChildren } from 'react';
    
    export interface WithRouterProps<T = ReturnType<typeof useParams>> extends PropsWithChildren {
      history: {
        back: () => void;
        goBack: () => void;
        location: ReturnType<typeof useLocation>;
        push: (url: string, state?: any) => void;
      };
      location: ReturnType<typeof useLocation>;
      params: T;
      searchParams: T;
      navigate: ReturnType<typeof useNavigate>;
    }
    
    const withRouter = <P extends object>(Component: ComponentType<P>) => {
      // 相当于给 MyCard组件添加各种props属性,还添加三个重要的属性params,location,navigate
      return (props: Omit<P, keyof WithRouterProps>) => {
        const location = useLocation();
        const params = useParams();
        const searchParams = useSearchParams();
        const navigate = useNavigate();
        const history = {
          back: () => navigate(-1),
          goBack: () => navigate(-1),
          location,
          push: (url: string, state?: any) => navigate(url, { state }),
          replace: (url: string, state?: any) =>
            navigate(url, {
              replace: true,
              state
            })
        };
        return (
          <Component
            history={history}
            location={location}
            navigate={navigate}
            params={params}
            searchParams={searchParams}
            {...(props as P)}
          />
        );
      };
    };
    
    export default withRouter;
    

    路由拦截和路由配置 router/index.tsx

    import Login from '../pages/login/index';
    import Layout from '../pages/layout/index';
    import Home from '../pages/home/index';
    import Other from '../pages/other/index';
    import NotPage from '../pages/404/index';
    import { useRoutes, Navigate, RouteObject } from 'react-router-dom';
    import React from 'react';
    
    function getItem(
      path: RouteObject['path'],
      element: RouteObject['element'],
      children?: RouteObject['children']
    ): RouteObject {
      return {
        path,
        element,
        children
      } as RouteObject;
    }
    
    // 设置路由的地方
    const routers: RouteObject[] = [
      getItem('/login', <Login></Login>),
      getItem('/', <Layout></Layout>, [
        getItem('/', <Home></Home>),
        getItem('/other', <Other></Other>)
      ]),
      getItem('*', <NotPage></NotPage>)
    ];
    
    const isUserAuthenticated = () => {
      const token = localStorage.getItem('token');
      return token && token.length > 0;
    };
    
    const AuthRoute: React.FC<React.PropsWithChildren> = (props: React.PropsWithChildren) => {
      if (!isUserAuthenticated()) {
        return (
          <div>
            {props.children}
            <Navigate to="/login" />
          </div>
        );
      }
      return <div>{props.children}</div>;
    };
    AuthRoute.displayName = 'AuthRoute';
    
    const RouterInterceptor: React.FC = (props: React.PropsWithChildren) => {
      const routerEls = useRoutes(routers);
      return <AuthRoute>{routerEls}</AuthRoute>;
    };
    RouterInterceptor.displayName = 'RouterInterceptor';
    export default RouterInterceptor;
    

    登录页面login/index.tsx

    import React from 'react';
    import { Input, Form, Button, Space } from 'antd';
    import withRouter, { WithRouterProps } from '@/components/withRouter';
    
    const Login: React.FC<WithRouterProps> = (props: WithRouterProps) => {
      const [form] = Form.useForm();
      const layout = {
        labelCol: { span: 8 },
        wrapperCol: { span: 16 },
        style: { maxWidth: 200 }
      };
    
      const tailLayout = {
        wrapperCol: { offset: 8, span: 16 }
      };
      const onFinish = (values: { token: string }) => {
        localStorage.setItem('token', values.token);
        props.history.push('/');
      };
    
      const onReset = () => {
        form.resetFields();
      };
    
      return (
        <div>
          <Form {...layout} form={form} onFinish={onFinish}>
            <Form.Item name="token" label="Token" rules={[{ required: true }]}>
              <Input />
            </Form.Item>
            <Form.Item {...tailLayout}>
              <Space>
                <Button type="primary" htmlType="submit">
                  登录
                </Button>
                <Button htmlType="button" onClick={onReset}>
                  重置
                </Button>
              </Space>
            </Form.Item>
          </Form>
        </div>
      );
    };
    export default withRouter(Login);
    

    主布局页面layout/index.tsx

    import React from 'react';
    import type { MenuProps } from 'antd';
    import { Layout, Menu } from 'antd';
    import { Outlet } from 'react-router-dom';
    import withRouter, { WithRouterProps } from '@/components/withRouter';
    import { HomeOutlined, UnorderedListOutlined } from '@ant-design/icons';
    import './index.scss';
    
    const { Header, Content, Footer, Sider } = Layout;
    
    type MenuItem = Required<MenuProps>['items'][number];
    
    function getItem(
      label: React.ReactNode,
      key: React.Key,
      icon?: React.ReactNode,
      children?: MenuItem[],
      type?: 'group'
    ): MenuItem {
      return {
        key,
        icon,
        children,
        label,
        type
      } as MenuItem;
    }
    
    class LayoutContent extends React.Component<WithRouterProps> {
      state: Readonly<{
        menuIndex: string;
        items: MenuItem[];
        collapsed: boolean;
        contentMarginLeft: number;
      }>;
    
      constructor(props: WithRouterProps) {
        super(props);
        const { location } = this.props;
        const menuItems: MenuItem[] = [
          getItem('Home', '/', <HomeOutlined />),
          getItem('Table', '/table', <UnorderedListOutlined />)
        ];
        this.state = {
          menuIndex: location.pathname,
          items: menuItems,
          collapsed: false,
          contentMarginLeft: 200
        };
      }
    
      onClick(index: { key: string }) {
        console.log(index);
        this.props.navigate(index.key);
      }
    
      onCollapse(value: boolean) {
        this.setState({ collapsed: value });
        this.setState({ contentMarginLeft: value ? 80 : 200 });
      }
    
      componentDidUpdate(
        prevProps: Readonly<WithRouterProps>,
        prevState: Readonly<object>,
        snapshot?: any
      ): void {
        if (this.props.location.pathname !== this.state.menuIndex) {
          this.setState({ menuIndex: this.props.location.pathname });
        }
      }
    
      render() {
        return (
          <Layout style={{ minHeight: '100vh' }} hasSider>
            <Sider
              collapsible
              collapsed={this.state.collapsed}
              style={{
                overflow: 'auto',
                height: '100vh',
                position: 'fixed',
                left: 0,
                top: 0,
                bottom: 0
              }}
              onCollapse={(value) => this.onCollapse(value)}
            >
              <div className="demo-logo-vertical" />
              <Menu
                theme="dark"
                onClick={this.onClick.bind(this)}
                selectedKeys={[this.state.menuIndex]}
                mode="inline"
                items={this.state.items}
              />
            </Sider>
            <Layout style={{ marginLeft: this.state.contentMarginLeft }}>
              <Header style={{ padding: 0, backgroundColor: '#fff' }} />
              <Content style={{ margin: '0 16px' }}>
                <Outlet />
              </Content>
              <Footer style={{ textAlign: 'center' }}>
                Demo ©{new Date().getFullYear()} Created by Demo
              </Footer>
            </Layout>
          </Layout>
        );
      }
    }
    
    export default withRouter(LayoutContent);
    
    
    // index.scss
    .demo-logo-vertical {
      height: 32px;
      margin: 16px;
      background: rgba(255, 255, 255, 0.2);
      border-radius: 6px;
    }
    

    首页redux样例home/index.tsx

    import React, { useState } from 'react';
    import { useSelector, useDispatch } from 'react-redux';
    import { increment, decrement, CounterState } from '@/features/counter/counterSlice';
    import { Button } from 'antd';
    
    const Home: React.FC = () => {
      const count = useSelector((state: { counter: CounterState }) => state.counter.value);
      const dispatch = useDispatch();
      return (
        <div>
          <h3>这个是redux的例子:</h3>
          <div style={{ display: 'flex' }}>
            <Button aria-label="Increment value" onClick={() => dispatch(increment())}>
              Increment
            </Button>
            <div style={{ minWidth: '20px', lineHeight: '30px', textAlign: 'center' }}>{count}</div>
            <Button aria-label="Decrement value" onClick={() => dispatch(decrement())}>
              Decrement
            </Button>
          </div>
        </div>
      );
    };
    export default Home;
    

    404/index.tsx

    import React from 'react';
    const NotPage: React.FC = () => {
      return <div>404</div>;
    };
    export default NotPage;
    

    ant Form + Table结合的页面 table/index.tsx

    import React, { useState } from 'react';
    import { Button, Table, Form, Row, Col, Space, Input } from 'antd';
    import { DownOutlined } from '@ant-design/icons';
    import type { TableColumnsType, TablePaginationConfig } from 'antd';
    
    interface DataType {
      key: React.Key;
      name: string;
      age: number;
      address: string;
    }
    
    const columns: TableColumnsType<DataType> = [
      {
        title: 'Name',
        dataIndex: 'name'
      },
      {
        title: 'Age',
        dataIndex: 'age'
      },
      {
        title: 'Address',
        dataIndex: 'address'
      }
    ];
    
    const data: DataType[] = [];
    for (let i = 0; i < 46; i++) {
      data.push({
        key: i,
        name: `Edward King ${i}`,
        age: 32,
        address: `London, Park Lane no. ${i}`
      });
    }
    
    interface SearchFormFields {
      label: string;
      name: string;
      element: any;
      rule?: object[];
    }
    
    const fields: SearchFormFields[] = [
      {
        label: 'Name',
        name: 'name',
        element: <Input placeholder="请输入" />
      },
      {
        label: 'Age',
        name: 'age',
        element: <Input placeholder="请输入" />
      },
      {
        label: 'Address',
        name: 'address',
        element: <Input placeholder="请输入" />
      }
    ];
    
    const TableList: React.FC = () => {
      const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
      const [loading, setLoading] = useState(false);
      const [expand, setExpand] = useState(false);
    
      const [form] = Form.useForm();
    
      const getFields = () => {
        const count = expand ? fields.length : 3;
        const children = [];
        for (let i = 0; i < count; i++) {
          const element = fields[i];
          children.push(
            <Col span={8} key={i}>
              <Form.Item name={element.name} label={element.label} rules={element.rule}>
                {element.element}
              </Form.Item>
            </Col>
          );
        }
        return children;
      };
    
      const pageSetting: TablePaginationConfig = {
        showSizeChanger: true,
        showQuickJumper: true,
        showTotal: (total) => `Total ${total} items`
      };
    
      const start = () => {
        setLoading(true);
        // ajax request after empty completing
        setTimeout(() => {
          setSelectedRowKeys([]);
          setLoading(false);
        }, 1000);
      };
    
      const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
        console.log('selectedRowKeys changed: ', newSelectedRowKeys);
        setSelectedRowKeys(newSelectedRowKeys);
      };
    
      const rowSelection = {
        selectedRowKeys,
        onChange: onSelectChange
      };
    
      const formStyle: React.CSSProperties = {
        maxWidth: 'none',
        padding: 24
      };
    
      const onFinish = (values: any) => {
        console.log('Received values of form: ', values);
      };
    
      const hasSelected = selectedRowKeys.length > 0;
      return (
        <div>
          <Form form={form} name="advanced_search" style={formStyle} onFinish={onFinish}>
            <Row gutter={24}>{getFields()}</Row>
            <div style={{ textAlign: 'right' }}>
              <Space size="small">
                <Button type="primary" htmlType="submit">
                  Search
                </Button>
                <Button
                  onClick={() => {
                    form.resetFields();
                  }}
                >
                  Clear
                </Button>
                {fields.length > 3 ? (
                  <a
                    style={{ fontSize: 12 }}
                    onClick={() => {
                      setExpand(!expand);
                    }}
                  >
                    <DownOutlined rotate={expand ? 180 : 0} /> Collapse
                  </a>
                ) : (
                  <span></span>
                )}
              </Space>
            </div>
          </Form>
          <Table
            pagination={pageSetting}
            rowSelection={rowSelection}
            columns={columns}
            dataSource={data}
          />
        </div>
      );
    };
    export default TableList;
    

    相关文章

      网友评论

          本文标题:从零学习React+TS项目搭建(二)

          本文链接:https://www.haomeiwen.com/subject/drtexjtx.html