• 前端笔记


    React

    Create react app

    官网:https://create-react-app.dev/docs/getting-started

    中文:https://www.html.cn/create-react-app/docs/getting-started/

    创建项目

    创建ts项目:

    npx create-react-app my-app --template typescript

    ant design + ts:

    参考:https://ant.design/docs/react/use-with-create-react-app-cn

    yarn create react-app antd-demo-ts –-template typescript

    npx create-react-app antd-demo-ts –typescript

    yarn add antd

    npm run start

    1. 修改 src/App.tsx,引入 antd 的按钮组件。

    import { Button } from 'antd';

    <Button type="primary">Button</Button>

    1. 修改 src/App.css,在文件顶部引入 antd/dist/antd.css

    @import '~antd/dist/antd.css';

    使用scss

    安装node-sass就可以在项目直接使用:

    yarn add node-sass

    npm install node-sass –save

    使用less(虽然可用,但存在问题,暂时找不方案)

    初始化的项目不支持less,,不像scss,需要修改配置文件;先安装less插件:

    yarn add less less-loader

    暴露配置文件:

    npm run eject

    如果报错:Remove untracked files, stash or commit any changes, and try again.

    那么先提交:

    修改配置文件:

    const lessRegex = /\.less$/;

    const lessModuleRegex = /\.module\.less$/;

     

    {

      test: lessRegex,

      exclude: lessModuleRegex,

      use: getStyleLoaders({ importLoaders: 2 }, 'less-loader'),

    },

    {

      test: lessModuleRegex,

      use: getStyleLoaders(

        {

          importLoaders: 2,

          modules: true,

          getLocalIdent: getCSSModuleLocalIdent,

        },

        'less-loader'

      ),

     },

    添加.prettierrc.json

     

    别名配置

    1. 安装 react-app-rewired:

    npm install -S react-app-rewired

    1. package.json文件中的脚本替换成如下:
      1. 创建config-overrides.js
      2. 配置tsconfig.json
    3.     "scripts": {
    4.         "start": "react-app-rewired start",
    5.         "build": "react-app-rewired build",
    6.         "test": "react-app-rewired test",
    7.         "eject": "react-app-rewired eject"
    8.      }
    const path = require('path');
     
    module.exports = function override(config) {
      config.resolve = {
        ...config.resolve,
        alias: {
          ...config.alias,
          '@': path.resolve(__dirname, 'src'),
        },
      };
     
      return config;
    };

    "paths": {

    "@/*": ["./src/*"],

    }

    添加路由

    yarn add react-router-dom

    安装的是react-router-dom6 版本,与之前的旧版本用法很大区别参考:https://www.jianshu.com/p/7c777d5cd476

    使用:

    import { HashRouter, Routes, Route } from "react-router-dom";

    const App: React.FC = () => {

      return (

        <HashRouter>

          <Routes>

            <Route path="/" element={<Layout />}>

              <Route path="/" element={<Flow />} />

              <Route path="/flow" element={<Flow />} />

              <Route path="/matrix" element={<Matrix />} />

            </Route>

          </Routes>

        </HashRouter>

      );

    };

    export default App;

    子路由与跳页面:

    import { Outlet, useNavigate } from "react-router-dom";

      return (

    <div>

          <Button onClick={() => { navigate("/flow") }}>跳页面</Button>

          <Outlet /> // 子路由

        </div>

      );

    懒加载:

    import {lazy, Suspense} from 'react';

    const Matrix = lazy(() => import('@/pages/Matrix'));

    const Flow = lazy(() => import('@/pages/Flow'));

    <Route

     path="/flow"

     element={<Suspense fallback={<Loading />}><Flow /></Suspense>}

    />

    <Route

     path="/matrix"

     element={<Suspense fallback={<Loading />}><Matrix /></Suspense>}

    />

    暴露配置文件的配置方式

    npm run eject 需要全部提交暂存文件,才可以执行

    按需加载ant design 样式

    yarn add babel-plugin-import –D

    package.json:

        "plugins": [

          [

            "import",

            { "libraryName": "antd", "style": "css" }

          ]

    ]

    定制主题

    图1圈住代码:

    const lessRegex = /\.less$/;

    const lessModuleRegex = /\.module\.less$/;

    图2圈住代码:

    if (preProcessor === "less-loader") {

      loaders.push(

        {

          loader: require.resolve("resolve-url-loader"),

          options: {

            sourceMap: isEnvProduction && shouldUseSourceMap,

          },

        },

        {

          loader: require.resolve(preProcessor),

          options: {

            lessOptions: {

              sourceMap: true,

              modifyVars: {

                "@primary-color": "red",

              },

              javascriptEnabled: true,

            },

          },

        }

      );

    } else if (preProcessor) {

      // .....

    }

    图3圈住代码:

    {

      test: lessRegex,

      exclude: lessModuleRegex,

      use: getStyleLoaders(

        {

          importLoaders: 3,

          sourceMap: isEnvProduction && shouldUseSourceMap,

        },

        "less-loader"

      ),

      sideEffects: true,

    },

    {

      test: lessModuleRegex,

      use: getStyleLoaders(

        {

          importLoaders: 3,

          sourceMap: isEnvProduction && shouldUseSourceMap,

          modules: { getLocalIdent: getCSSModuleLocalIdent },

        },

        "less-loader"

      ),

    },

    问题:样式变量不能使用rem

    报错:

    别名配置

    "@": path.resolve(__dirname, "../src"),

    "paths": { "@/*": ["./src/*"] }

    Ant Design Pro

    初始化项目

    yarn create umi umi-app

    npx create-umi myapp

    cd umi-app && yarn

    启动:npm run start

    问题1:如果使用npm run dev 启动,会登录不上

    解决:使用npm run start

    问题2:初始化项目后,不知道为什么import react from ‘react’ 报错:找不到模块“react”或其相应的类型声明

    解决:重新打开vscode编辑器就没有

    使用mock数据

    官网:https://umijs.org/zh-CN/config#mock

    配置完成,保存后,会自动生成数据:

    禁用:

    mock: false

    也可以通过环境变量临时关闭:

    MOCK=none umi dev

    删除国际化

    1: npm run i18n-remove

    2: 删除locales文件夹

    删除用例测试

    删除:根目录下的tests文件夹

    删除:\src\e2e文件夹

    删除:配置文件:jest.config.js

    删除:下面配置

    设置浏览器title

    问题

    1. 如果1设置title: false,后那么3路由title设置也会无效
    2. 如果使用了plugin-layout插件, 那么只能用插件来设置title, 1、3设置都会失效,如果2没设置,那么会使用默认值 ant-design-pro
    1. 使用了plugin-layout插件,同时设置了1或者3,那title会闪烁,先变1/3,在变2;
    2. 如果左侧有菜单,ttitle的表现形式是 “菜单名称”+ “layout设置的title”

    解决

    https://beta-pro.ant.design/docs/title-landing-cn

    ProLayout 会根据菜单和路径来自动匹配浏览器的标题。可以设置 pageTitleRender=false 来关掉它。

    1. 如果项目由此至终都只需要一个title,那么可以这样设置:
    1. 如果需要根据路由来显示title,那么可以这样设置:

    保留1的配置,然后各自在路由上设置title:

    todo: 有个bug,就是在登录界面登进去,会显示config.js 上的title,刷新后才会显示路由设置的title, 可以让它们保持一致。

    3.       果不设置pageTitleRender: false,ttitle的表现形式是 “菜单名称”+ “layout设置的title”; pageTitleRender 可以是一个方法,返回字符串,就是浏览器的title,只是在浏览器刷新时候生效,切换页面,会被路由的title或者 config.ts 设置的title 覆盖。
    4.   当您想从 React 组件更改标题时,可以使用第三方库 React Helmet。react-helmet
    https://www.npmjs.com/package/react-helmet

    修改加载页

    首次进入的加载

    js 还没加载成功,但是 html 已经加载成功的 landing 页面:src\pages\document.ejs

    使用了 home_bg.png ,pro_icon.svg 和 KDpgvguMpGfqaHPjicRK.svg 三个带有品牌信息的图片,你可以按需修改他们。

    切换页面加载

    项目中打开了代码分割的话,在每次路由切换的时候都会进入一个加载页面。

    dynamicImport: {

      loading: '@ant-design/pro-layout/es/PageLoading',

    }

    业务中的加载

    等待用户信息或者鉴权系统的请求完成后才能展示页面。 getInitialState支持了异步请求,同时在请求时会停止页面的渲染。这种情况下加载页的。我们可以在 src\app.tsx 中配置:

    /** 获取用户信息比较慢的时候会展示一个 loading */

    export const initialStateConfig = {

      loading: <PageLoading />,

    };

    插件

    文档:https://umijs.org/zh-CN/docs/plugin

    全局数据

    插件:https://umijs.org/zh-CN/plugins/plugin-initial-state

    有 src/app.ts 并且导出 getInitialState 方法时启用

    本插件不可直接使用,必须搭配 @umijs/plugin-model 一起使用。

    getInitialState

    使用插件plugin-initial-state, 项目启动会先在app.tsx 执行getInitialState方法,是async,可以执行异步请求;返回数据后才会加载路由页面,数据可以全局使用。

    代码模板:

    export async function getInitialState(): Promise<{

      loading?: boolean;

      currentUser?: API.CurrentUser;

      fetchUserInfo?: () => Promise<API.CurrentUser | undefined>;

    }> {

      await Promise.resolve('')

      return {

        loading: false,

        currentUser: {}

      };

    }

    获取数据:

    import { useModel } from 'umi';

    const { initialState } = useModel('@@initialState');

    console.log(initialState?.currentUser);

    initialStateConfig

    initialStateConfig 是 getInitialState 的补充配置,getInitialState 支持异步的设置,在初始化没有完成之前我们展示了一个 loading,initialStateConfig 可以配置这个 loading。

    import { PageLoading } from '@ant-design/pro-layout';

    /** 获取用户信息比较慢的时候会展示一个 loading */

    export const initialStateConfig = {

      loading: <PageLoading />,

    };

    布局

    使用插件:plugin-layout

    插件文档:https://umijs.org/zh-CN/plugins/plugin-layout

    配置文档:https://procomponents.ant.design/components/layout/

    运行时配置布局:

    childrenRender

    这是文档找不到的配置,可以在每一个路由页面添加点东西:

    权限

    有 src/access.ts 时启用。约定了 src/access.ts 为我们的权限定义文件,需要默认导出一个方法,导出的方法会在项目初始化时被执行。该方法需要返回一个对象,对象的每一个值就对应定义了一条权限。如下所示:

    initialState 是通过初始化状态插件 @umijs/plugin-initial-state 提供的数据,你可以使用该数据来初始化你的用户权限。

    useAccess

    我们提供了一个 Hooks 用于在组件中获取权限相关信息,如下所示:

    import { useAccess } from 'umi';

    const access = useAccess();

    if (access.canReadFoo) {  }
    Access
    组件 <Access /> 对应用进行权限控制, 支持的属性如下:

    accessible:    Type: boolean 是否有权限,通常通过 useAccess 获取后传入进来。
    fallback:       Type: React.ReactNode无权限时的显示,默认无权限不显示任何内容。

    children:      Type: React.ReactNode有权限时的显示。

    import { useAccess, Access } from 'umi';

    const access = useAccess();

    <Access
      accessible={access.canReadFoo}
    fallback={<div>无权限显示</div>}>
    有权限显示
    </Access>

    菜单/路由

    type RouteType = {

      path?: string;

      component?: string | (() => any);

      wrappers?: string[];

      redirect?: string;

      exact?: boolean;

      routes?: any[];

      [k: string]: any;

    };

    interface MenuType {

      path?: string;

      component?: string;

      name?: string;

      icon?: string;

      target?: string;

      headerRender?: boolean;

      footerRender?: boolean;

      menuRender?: boolean;

      menuHeaderRender?: boolean;

      access?: string;

      hideChildrenInMenu?: boolean;

      hideInMenu?: boolean;

      hideInBreadcrumb?: boolean;

      flatMenu?: boolean;

    }

    type RoutersType = (RouteType & MenuType)[];

    菜单

    菜单可以根据routes.ts自动生成,参考:

    https://pro.ant.design/zh-CN/docs/new-page#%E5%9C%A8%E8%8F%9C%E5%8D%95%E4%B8%AD%E4%BD%BF%E7%94%A8-iconfont

    下面是routes配置中,关于菜单的配置说明:

    name
    • name:string 配置菜单的 name,不配置,不会显示菜单,配置了国际化,name 为国际化的 key。
    • icon:string 配置菜单的图标,默认使用 antd 的 icon 名,默认不适用二级菜单的 icon。
    • access:string 权限配置,需要预先配置权限
    • hideChildrenInMenu:true 用于隐藏不需要在菜单中展示的子路由。
    • layout:false 隐藏布局
    • hideInMenu:true 可以在菜单中不展示这个路由,包括子路由。
    • hideInBreadcrumb:true 可以在面包屑中不展示这个路由,包括子路由。
    • headerRender:false 当前路由不展示顶栏
    • footerRender:false 当前路由不展示页脚
    • menuRender: false 当前路由不展示菜单
    • menuHeaderRender: false 当前路由不展示菜单顶栏
    • flatMenu 子项往上提,只是不展示父菜单
    Icon
    access
    hideChildrenInMenu
    layout
    hideInMenu
    hideInBreadcrumb
    headerRender
    footerRender
    menuRender
    menuHeaderRender
    flatMenu 

    路由

    文档:https://umijs.org/zh-CN/docs/routing

    配置文件中通过 routes 进行配置,格式为路由信息的数组。

    import type { IConfigFromPlugins } from '@@/core/pluginConfig';

    type RoutersType = IConfigFromPlugins['routes'];

     

    const routers: RoutersType = [

      { exact: true, path: '/', component: 'index' },

      { exact: true, path: '/user', component: 'user' },

    ];

     

    export default routers;

    path

    配置可以被 path-to-regexp@^1.7.0 理解的路径通配符。

    component

    React 组件路径。可以是绝对路径,也可以是相对路径,如果是相对路径,会从 src/pages 开始找起。可以用 @,也可以用 ../。比如

     component: '@/layouts/basic'

     component: '../layouts/basic'

    exact

    Default: true 表示是否严格匹配,即 location 是否和 path 完全对应上

        // url 为 /one/two 时匹配失败
        { path: '/one', exact: true },
        
        // url 为 /one/two 时匹配成功
        { path: '/one' },
        { path: '/one', exact: false },
    routes

    配置子路由,通常在需要为多个路径增加 layout 组件时使用

        {
          path: '/',
          component: '@/layouts/index',
          routes: [
            { path: '/list', component: 'list' },
            { path: '/admin', component: 'admin' },
          ]
        }

    在 src/layouts/index 中通过 props.children 渲染子路由

    export default (props) => {
      return <div style={{ padding: 20 }}>{ props.children }</div>;
    }

    这样,访问 /list 和 /admin 就会带上 src/layouts/index 这个 layout 组件

    redirect

    重定向,例子:

    { exact: true, path: '/', redirect: '/list' }

    访问 / 会跳转到 /list,并由 src/pages/list 文件进行渲染

    wrappers

    配置路由的高阶组件封装,比如,可以用于路由级别的权限校验:

    export default {

      routes: [

        { path: '/user', component: 'user', wrappers: ['@/wrappers/auth'] },

      ],

    };

    然后在 src/wrappers/auth 中:

    import { Redirect } from 'umi';

    export default (props: any) => {

      const isLogin = false;

      if (isLogin) {

        return <div>{props.children}</div>;

      } else {

        return <Redirect to="/login" />;

      }

    };

    target
    {
    // path 支持为一个 url,必须要以 http 开头
    path: 'https://pro.ant.design/docs/getting-started-cn',
    target: '_blank', // 点击新窗口打开
    name: '文档',
    }
    页面跳转

    import { history } from 'umi';

    history.push('/list');

    history.push('/list?a=b');

    history.push({ pathname: '/list', query: { a: 'b' } });

    history.goBack();

    link: 只用于单页应用的内部跳转,如果是外部地址跳转请使用 a 标签

    import { Link } from 'umi';

    <Link to="/users">Users Page</Link>

    获取参数

    import { useLocation, history } from 'umi';

      const query = history.location.query;

     

    const location = useLocation();

    console.log(location.query); // 不知道为什么类型没有提示

    样式/图片

    样式

    1. 约定 src/global.css 为全局样式,如果存在此文件,会被自动引入到入口文件最前面,可以用于覆盖ui组件样式。
    2. Umi 会自动识别 CSS Modules 的使用,你把他当做 CSS Modules 用时才是 CSS Modules。
    3. 内置支持 less,不支持 sass 和 stylus,但如果有需求,可以通过 chainWebpack 配置或者 umi 插件的形式支持。

    图片/svg

    export default () => <img src={require('./foo.png')} />

    export default () => <img src={require('@/foo.png')} />

    import { ReactComponent as Logo } from './logo.svg'

    <Logo width={90} height={120} />

    import logoSrc from './logo.svg'

    <img src={logoSrc} alt="logo" />

    相对路径引用: background: url(./foo.png);

    支持别名: background: url(~@/foo.png);

    Umijs api

    官网:https://umijs.org/zh-CN/api

    dynamic

    动态加载组件。使用场景:组件体积太大,不适合直接计入 bundle 中,以免影响首屏加载速度

    // AsyncHugeA.tsx

    import { dynamic } from 'umi';

    export default dynamic({
      loader: async function () {
        // 注释 webpackChunkName:webpack 将组件HugeA以这个名字单独拆出去
        const { default: HugeA } = await import(
          /* webpackChunkName: "external_A" */ './HugeA'
        );
        return HugeA;
      }
    });
     
    // 使用:
    import AsyncHugeA from './AsyncHugeA';
    <AsyncHugeA />

    history

    获取信息
    // location 对象,包含 pathname、search 和 hash、query
    console.log(history.location.pathname);
    console.log(history.location.search);
    console.log(history.location.hash);
    console.log(history.location.query);
     
    跳转路由

    history.push('/list');

    history.push('/list?a=b');

    history.push({ pathname: '/list', query: { a: 'b' } });

    history.goBack();

    监听路由变化
    const unlisten = history.listen((location, action) => {
      console.log(location.pathname);
    });
    unlisten(); // 取消监听

    Link

    import { Link } from 'umi';
    <Link to="/courses?sort=name">Courses</Link>
    <Link to={{
    pathname: '/list',
    search: '?sort=name',
    hash: '#the-hash',
    state: { fromDashboard: true },
    }}>List</Link>
     
    // 跳转到指定 /profile 路由,附带所有当前 location 上的参数
    <Link to={ loca => {return { ... loca, pathname: '/profile' }}}/>
     
    // 转到指定 /courses 路由,替换当前 history stack 中的记录
    <Link to="/courses" replace />

    NavLink

    特殊版本的 <Link /> 。当指定路由(to=指定路由)命中时,可以附着特定样式。

    https://umijs.org/zh-CN/api#link

    Prompt

    <Prompt message="你确定要离开么?" />
    {/* 用户要跳转到首页时,提示一个选择 */}
    <Prompt message={loc => loc.pathname !== '/' ? true : `您确定要跳转到首页么?`}/>
    {/* 根据一个状态来确定用户离开页面时是否给一个提示选择 */}
    <Prompt when={formIsHalfFilledOut} message="您确定半途而废么?" />
    
    

    withRouter

    高阶组件,可以通过 withRouter 获取到 historylocationmatch 对象withRouter(({ history, location, match }) => {})

    useHistory

    hooks,获取 history 对象

    useLocation

    hooks,获取 location 对象

    useParams

    hooks,获取 params 对象。 params 对象为动态路由(例如:/users/:id)里的参数键值对。

    Umijs 配置

    官网:https://umijs.org/zh-CN/config#alias

    proxy代理

      proxy: {

        '/api': {

          'target': 'http://jsonplaceholder.typicode.com/',

          'changeOrigin': true,

          'pathRewrite': { '^/api' : '' },

        }

      }

    访问 /api/users 就能访问到 http://jsonplaceholder.typicode.com/users 的数据

    alias别名

    export default { alias: { foo: '/tmp/a/b/foo'} };
    然后 import('foo'),实际上是 import('/tmp/a/b/foo')

    Umi 内置了以下别名:

    @,项目 src 目录

    @@,临时目录,通常是 src/.umi 目录

    umi,当前所运行的 umi 仓库目录

    base路由前缀

    Default: /

    设置路由前缀,通常用于部署到非根目录。

    比如,你有路由 / 和 /users,然后设置base为 /foo/,那么就可以通过 /foo/ 和 /foo/users 访问到之前的路由。

    publicPath

    Default: /

    配置 webpack 的 publicPath。当打包的时候,webpack 会在静态文件路径前面添加 publicPath 的值,当你需要修改静态文件地址时,比如使用 CDN 部署,把 publicPath 的值设为 CDN 的值就可以。

    如果你的应用部署在域名的子路径上,例如 https://www.your-app.com/foo/,你需要设置 publicPath 为 /foo/,如果同时要兼顾开发环境正常调试,你可以这样配置:

    publicPath: process.env.NODE_ENV === 'production' ? '/foo/' : '/',

    chainWebpack webpack配置

    通过 webpack-chain 的 API 修改 webpack 配置。

    dynamicImport

    是否启用按需加载,即是否把构建产物进行拆分,在需要的时候下载额外的 JS 再执行。关闭时,只生成一个 js 和一个 css,即 umi.js 和 umi.css。优点是省心,部署方便;缺点是对用户来说初次打开网站会比较慢。

    包含以下子配置项: loading, 类型为字符串,指向 loading 组件文件

    externals

    favicon

    Type: string: 配置 favicon 地址(href 属性)。

    配置:

    favicon: '/ass/favicon.ico',

    生成:

    <link rel="shortcut icon" type="image/x-icon" href="/ass/favicon.ico" />

    fastRefresh 

    • Type: object

    快速刷新(Fast Refresh),开发时可以保持组件状态,同时编辑提供即时反馈

    hash

    links/metas/styles

    配置额外的 link 标签。

    配置额外的 meta 标签。数组中可以配置key:value形式的对象。

    Default: [] 配置额外 CSS。

    headScripts/scripts

    headScripts: 配置 <head> 里的额外脚本,数组项为字符串或对象。

    Scripts: 同 headScripts,配置 <body> 里的额外脚本。

    ignoreMomentLocale

    • Type: boolean
    • Default: false

    忽略 moment 的 locale 文件,用于减少尺寸。

    mock

    theme

    配置主题,实际上是配 less 变量。

    export default {
      theme: {
        '@primary-color': '#1DA57A',
      },
    };

    Theme for antd: https://ant.design/docs/react/customize-theme-cn

    title

    配置标题。(设置false可以关闭)

     title: '标题',

    React优化

    React.memo

    React.memo()是一个高阶函数,它与 React.PureComponent类似,但是一个函数组件而非一个类。

    React.memo()可接受2个参数,第一个参数为纯函数的组件,第二个参数用于对比props控制是否刷新,与shouldComponentUpdate()功能类似。

    一般可以配个useCallback使用,防止使用onClick={() => { //…. }}导致子组件每次渲染

    useCallback

    问1:

    回到刚刚例子,这次传递一个函数callback, 你会发现,React.memo无效:

    解决:那就是使用useCallback包裹函数:

    const callback = useCallback((e: any) => setnum(Math.random()), []);

    修改后,你会发现和第一个例子那样,memo包裹的,如果callback不变,只会在第一次触发;

    问2:

    useCallback 第二个参数,是依赖项, 如果依赖项变化, 那么函数还是会频繁创建, 导致React.meno包裹的组件重新渲染. 有什么方法可以保证函数地址一值不变?

    官方临时提议,使用ref, 变量重新缓存useCallback需要访问的值:

    最后抽个自定义hooks:

    再优化, 每次都传递依赖项,太麻烦,可以优化下,不需要传递deps,传递deps目的就是为了依赖变化,重新复制当前函数,如果次次都赋值,就不需要传递.

    阿里开源的 react hooks 工具库 ahooks中的usePersistFn(3.x 是useMemoizedFn )就是这种思路实现不需要传递依赖项的。源码:

    源码地址:

    https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useMemoizedFn/index.ts

    type PickFunction<T extends noop> = (

      this: ThisParameterType<T>,

      ...args: Parameters<T>

    ) => ReturnType<T>;

    type noop = (this: any, ...args: any[]) => any;

     

    function useMemoizedFn<T extends noop>(fn: T) {

      const fnRef = useRef<T>(fn)

      fnRef.current = useMemo(() => fn, [fn])

     

      const memoizedFn = useRef<PickFunction<T>>()

      if (!memoizedFn.current) {

        memoizedFn.current = function(this, ...args) {

          return fnRef.current.apply(this, args)

        }

      }

     

      return memoizedFn.current

    }

     

    问3:

    有一个问题,如果需要传递额外的参数,怎么办?例如列表循环,需要传递事件本身参数,还有当前的index?

    为了接受子组件的参数,我们通常下面的写法,但是你会发现,每次父组件更新,子组件都会更新,因为{ () => {//xxx} } 每次都会生新函数.

    那么有什么办法,可以做到父组件更新,只要props不变,就不影响子组件,然后还可以接受子组件传递的参数呢? 结果是暂时想不到,曾经以为下面写法行,结果还是不行,这样写,只是徒增理解难度:

    常用库

    ahooks

    git: https://github.com/alibaba/hooks

    文档: https://ahooks.js.org/zh-CN/guide

    useMemoizedFn

    理论上,可以使用 useMemoizedFn 完全代替 useCallback。

    useCallback 来记住一个函数,但是在第二个参数 deps 变化时,会重新生成函数,导致函数地址变化。

    useMemoizedFn,可以省略第二个参数 deps,同时保证函数地址永远不会变化。

    const [state, setState] = useState('');
    // func 地址永远不会变化
    const func = useMemoizedFn(() => {
      console.log(state);
    });
     
    原理就是使用useRef,每次父组件更新,current都指向新的回调函数;然后再创建另一个ref,值是一个函数,函数里执行第一个ref缓存的函数. 源码:

    https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useMemoizedFn/index.ts

    useSetState

    用法与 class 组件的 this.setState 基本一致。意思是setStates是合并对象,而不是替换对象;

    import { useSetState } from 'ahooks';

    const [state, setState] = useSetState<State>({ hello: '',count: 0 });
    <button onClick={() => setState({ hello: 'world' })}>set hello</button>
    原理其实就是重写在setState方法基础上,重新封装,通过setState能够接受函数作为参数,获得上一个props,然后合并返回,这样就可以达到效果.
    
    

    useReactive

    数据状态不需要写useState,直接修改属性即可刷新视图。

    const state = useReactive({
      count: 0,
      inputVal: '',
      obj: { value: '' }
    });
    <button onClick={() => state.count--}>state.count--</button>
    <input onChange={(e) => (state.obj.value = e.target.value)} />

    原理使用es6 Proxy对象,劫持对象上属性;

    在get的时候, 递归创建Proxy对象,这样就能让所有对象属性都劫持;

    在set和delete的时候, 先执行原生的逻辑,然后再强制触发页面的更新(useUpdate)

    源码:

    https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useReactive/index.ts#L48

    useUpdate

    useUpdate 会返回一个函数,调用该函数会强制组件重新渲染。

    import { useUpdate } from 'ahooks';

    const update = useUpdate();

    <button onClick={update}>update</button>
    原就是使用useState新建一个变量,然后返回一个函数,函数的逻辑就是修改变量,强制触发页面更新;
    源码:
    https://github.com/alibaba/hooks/blob/master/packages/hooks/src/useUpdate/index.ts

    useLockFn

    用于给一个异步函数增加竞态锁,防止并发执行。

    说点人话,就是点击保存的时候,如果需要保存成功后,才能继续保存,那么就使用它;

    import { useLockFn } from 'ahooks';

      const submit = useLockFn(async () => {
        message.info('Start to submit');
        await mockApiRequest();
        setCount((val) => val + 1); // await没有完成多次点击无效
        message.success('Submit finished');
      });

    <button onClick={submit}>Submit</button>

    原理也很简单,就是利用useRef, 创建一个标识,初始化false

    当触发函数,设置true,等异步执行完毕,或者异常,就重新设置false

    标识为true,那函数就不往下执行

    useThrottleFn / useDebounceFn

    频繁调用 run,但只会每隔 500ms 执行一次相关函数。

    import { useThrottleFn } from 'ahooks';

      const { run } = useThrottleFn(
        () => setValue(value + 1),
        { wait: 500 },
      );
    <button onClick={run}>Click fast!</button>
    
    

    useLocalStorageState/useSessionStorageState

    将状态存储在 localStorage 中的 Hook 。

    import { useLocalStorageState } from 'ahooks';

    const [message, setMessage] = useLocalStorageState('storage-key1', {
    defaultValue: 'Hello~' 
    });
    const [value, setValue] = useLocalStorageState(' storage-key2', {
    defaultValue: defaultArray,
    });
    
    
    

    可能你不需要默认的 JSON.stringify/JSON.parse 来序列化,;

    useLocalStorageState 在往 localStorage 写入数据前,会先调用一次 serializer在读取数据之后,会先调用一次 deserializer

    useUpdateEffect 

    useUpdateEffect 用法等同于 useEffect,会忽略首次执行,只在依赖更新时执行

    原理就是创建一个ref,首次渲染设置false, 运行的第一次设置为true;

    往后就是执行正常的逻辑

    useEventEmitter

    多个组件之间进行事件通知;

    通过 props 或者 Context ,可以将 event$ 共享给其他组件。

    调用 EventEmitter 的 emit 方法,推送一个事件

    调用 useSubscription 方法,订阅事件。

    const event$ = useEventEmitter()

    event$.emit('hello')

    event$.useSubscription(val => {
      console.log(val)
    })

    在组件多次渲染时,每次渲染调用 useEventEmitter 得到的返回值会保持不变,不会重复创建 EventEmitter 的实例。useSubscription 会在组件创建时自动注册订阅,并在组件销毁时自动取消订阅。

    例子:

    import { useEventEmitter } from 'ahooks';
    import { EventEmitter } from 'ahooks/lib/useEventEmitter';

    // 父组件有2个子组件:

    const focus$ = useEventEmitter();

    <MessageBox focus$={focus$} />
    <InputBox focus$={focus$} />
    子组件1:
    const InputBox: FC<{ focus$: EventEmitter<void> }> = (props) => {
      props.focus$.useSubscription((‘参数’) => {});
    };
    子组件2:
    const InputBox: FC<{ focus$: EventEmitter<void> }> = (props) => {
      props.focus$.emit(‘参数’);
    };

    Immutable

    用于保存原始对象,修改对象后,不会更新原始对象的值

    GitHub: https://github.com/immutable-js/immutable-js

    文档:

    https://blog.csdn.net/m0_37527015/article/details/84338831?spm=1001.2101.3001.6650.2&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2.pc_relevant_default&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-2.pc_relevant_default&utm_relevant_index=3

     Vue

    Vue3常用api

    defineEmits  (setup定义emits)

    const emit = defineEmits<{

      (e: 'change', id: number): void

      (e: 'update', value: string): void

    }>()

    defineProps   (setup定义props)

    withDefaults(defineProps<{

      foo: string

      bar?: number

      msg: string

    }>(), {

      msg: '',

      bar: 1,

      foo: '000'

    })

    defineExpose  (setup定义暴露出去的属性)

    const a = 1

    const b = ref(2)

    defineExpose({ a, b})

    useSlots , useAttrs

    对应:$slots 和 $attrs,因为在模板中可以直接访问,所以很少使用。

    import { useSlots, useAttrs } from 'vue'

    const slots = useSlots()

    const attrs = useAttrs()

    inheritAttrs

    <style module>

    <template>
      <p :class="$style.red">This should be red</p>
    </template>
    <style module>
    .red { color: red;}
    </style>

    动态样式

    <script setup lang="ts">

    const theme = {

      color: 'red'

    }

    </script>

    <template>

      <p>hello</p>

    </template>

    <style scoped>

    p {

      color: v-bind("theme.color");

    }

    </style>

    开发前配置

    Yarn:          npm I yarn -g

    淘宝镜像:    npm i -g cnpm --registry=https://registry.npm.taobao.org

    vscode不能使用cnpm:

    右击VSCode图标,选择以管理员身份运行;

    在终端中执行get-ExecutionPolicy,显示Restricted,表示状态是禁止的;

    插件

    Volar

    Vue3 代码格式工具

    报错:

    解决:

    // tsconfig.json
    {
      "compilerOptions": {
        "types": [
          "vite/client", // if using vite
        ]
      }
    }

    ESLint

    官网:http://eslint.cn/docs/user-guide/configuring

    安装:yarn add -D eslint

    初始化:npx eslint –init

    初始化之后,自动安装eslint-plugin-vue@latest, @typescript-eslint/eslint-plugin@latest, @typescript-eslint/parser@latest;同时,项目根目录回出现.eslintrc.js 文件;

    setup语法糖报错:

    解决:添加配置 parser: 'vue-eslint-parser',

    报错:The template root requires exactly one element.eslintvue/no-multiple-template-root意思是说模板根只需要一个元素

    解决:'plugin:vue/essential'  -> 'plugin:vue/vue3-essential'

      extends: [

        'eslint:recommended',

        'plugin:vue/vue3-essential',

        'plugin:@typescript-eslint/recommended',

      ],

    配置说明:rules

    l   "off" 或 0 - 关闭规则

    l   "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)

    l   "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)

    配置定义在插件中的一个规则的时候,你必须使用 插件名/规则ID 的形式:

    临时禁止规则出现警告:

    /* eslint-disable */
    /* eslint-disable no-alert, no-console */

    .eslintrc.json配置:

    {

      "root": true,

      "env": {

        "es2021": true,

        "node": true,

        "browser": true

      },

      "globals": {

        "node": true

      },

      "extends": [

        "plugin:prettier/recommended"

      ],

      "parser": "vue-eslint-parser",

      "parserOptions": {

        "parser": "@typescript-eslint/parser",

        "ecmaVersion": 12,

        "sourceType": "module"

      },

      "plugins": ["@typescript-eslint"],

      "ignorePatterns": ["types/env.d.ts", "node_modules/**", "**/dist/**"],

      "rules": {

        "@typescript-eslint/no-unused-vars": "error",

        "@typescript-eslint/no-var-requires": "off",

        "@typescript-eslint/consistent-type-imports": "error",

        "@typescript-eslint/explicit-module-boundary-types": "off",

        "vue/singleline-html-element-content-newline": "off",

        "vue/multiline-html-element-content-newline": "off",

        "vue/no-v-html": "off",

        "space-before-blocks": "warn",

        "space-before-function-paren": "error",

        "space-in-parens": "warn",

        "no-whitespace-before-property": "off",

        "semi": ["error", "never"],

        "quotes": ["warn", "single"]

      }

    }

     

    EditorConfig for vs code

    配置的代码规范规则优先级高于编辑器默认的代码格式化规则。如果我没有配置editorconfig,执行的就是编辑器默认的代码格式化规则;如果我已经配置了editorConfig,则按照我设置的规则来,从而忽略浏览器的设置。

    对应配置.editorconfig:

    root = true

    [*]

    charset = utf-8

    # end_of_line = lf

    indent_size = 2

    indent_style = space

    insert_final_newline = true

    ij_html_quote_style = double

    max_line_length = 120

    tab_width = 2

    # 删除行尾空格

    trim_trailing_whitespace = true

    Prettier - Code formatter

    前端代码格式化工具,对应.prettierrc.json配置:

    官网:https://prettier.io/docs/en/options.html

    以下是配置说明:

    printWidth // 默认80,一行超过多少字符换行

    tabWidth // 默认2,tab键代表2个空格

    useTabs // 默认false, 用制表符而不是空格缩进行

    semi  // 默认true, 使用分号

    singleQuote // 默认false, 使用单引号

    quoteProps // 默认 as-needed

    jsxSingleQuote // 默认false, 在JSX中使用单引号而不是双引号。

    trailingComma

    // 默认es5: 在es5尾随逗号(对象、数组等); ts中的类型参数中没有尾随逗号

    // node: 不尾随

    // all: 所有都尾随

    bracketSpacing // 默认true;对象文字中括号之间的空格

    bracketSameLine // 默认 false

    arrowParens // 默认always;函数参数周围包含括号,可选avoid

    vueIndentScriptAndStyle

    // 默认false;是否缩进Vue文件中<script>和<style>标记内的代码

    {

      "printWidth": 100, // 一行超过多少字符换行

      "tabWidth": 2, // tab键代码2个空格

      "useTabs": false, // 用制表符而不是空格缩进行

      "semi": false,

      "singleQuote": true,

      "vueIndentScriptAndStyle": true,

      "quoteProps": "as-needed",

      "bracketSpacing": true,

      "trailingComma": "es5",

      "jsxBracketSameLine": true,

      "jsxSingleQuote": false,

      "arrowParens": "always",

      "insertPragma": false,

      "requirePragma": false,

      "proseWrap": "never",

      "htmlWhitespaceSensitivity": "ignore",

      "endOfLine": "auto",

      "rangeStart": 0

    }

     

    Chinese (Simplified) (简体中文)

    中文插件

    其他包

    有些插件需要一些包配合使用:

    cnpm install @typescript-eslint/eslint-plugin @typescript-eslint/parser @vitejs/plugin-vue eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-vue prettier vue-eslint-parser -D

    设置快捷键

    自定义代码片段

    {

      "v3-setup": {

        "scope": "vue",

        "prefix": "v3-setup",

        "body": [

          "<script setup lang='ts'>\nimport { ref } from 'vue'\n${1}\nwithDefaults(defineProps<{}>(), {})\n\ndefineEmits<{\n\t(e: 'change', id: number): void\n}>()\n\n</script>\n\n<template>\n</template>\n\n<style scoped>\n</style>"

        ]

      },

     

      "v3-getCurrentInstance": {

        "scope": "javascript,typescript",

        "prefix": "v3-getCurrentInstance",

        "body": [

          "import { getCurrentInstance } from 'vue'\n\nconst internalInstance = getCurrentInstance()\n"

        ]

      },

     

      "v3-computed": {

        "scope": "javascript,typescript",

        "prefix": "v3-computed",

        "body": [

          "const $1 = computed(() => {\n\treturn $2\n})"

        ]

      },

     

      "v3-defineEmits": {

        "scope": "javascript,typescript",

        "prefix": "v3-emits",

        "body": [

          "const ${1:emit} = defineEmits<{\n\t(e: '${2:change}'): ${3:void}\n}>()"

        ]

      },

     

      "v3-defineProps": {

        "scope": "javascript,typescript",

        "prefix": "v3-props",

        "body": [

          "defineProps<$0>()\n"

        ]

      },

     

      "l1-setTimeout": {

        "scope": "javascript,typescript",

        "prefix": "l1-sett",

        "body": [

          "const ${1:timer} = setTimeout(() => {\n\t$3\n}, ${2:60})"

        ]

      },

     

      "l1-map": {

        "scope": "javascript,typescript",

        "prefix": "l1-map",

        "body": [

          "${1:arr}.${2:map}((item, index) => {\n\t${3}\n})"

        ]

      },

     

      "l1-reduce": {

        "scope": "javascript,typescript",

        "prefix": "l1-reduce",

        "body": [

          "${1:arr}.reduce((data, cur) => {\n\t${2}\n\treturn data\n}, {})",

        ]

      },

     

      "l1-promise": {

        "scope": "javascript,typescript",

        "prefix": "l1-promise",

        "body": [

          "return new Promise((resolve, reject) => {\n\t${1}\n})",

        ]

      }

    }

    保存自动格式化

    "editor.formatOnSave": true,

    创建项目

    yarn create vite

    cd 项目名称

    yarn

    yarn dev

    配置别名

    import { defineConfig } from 'vite'

    import vue from '@vitejs/plugin-vue'

    import * as path from 'path'

    const resovle = (p:string) => {

      return path.resolve(__dirname, p)

    }

     

    export default defineConfig({

      plugins: [vue()],

      resolve: {

        alias: {

          '@': resovle('./src')

        }

      }

    })

    如果发现引入path报错 “找不到模块“path”或其相应的类型声明”

    解决:cnpm install @types/node -D

    还需要配置tsconfig.json:(配置完成后,会自动引入本地模块)

        "lib": ["esnext", "dom"],

        "baseUrl": ".",

        "paths": {

          "@/*": ["./src/*"]

        }

    引入vue-router4

    安装:cnpm install vue-router@4 -S

    路由文件目录结构:

    main.ts上引用:

    import router from './router'

    const app = createApp(App)

    app.use(router)

    app.mount('#app')

    Index.ts 全局引入module下的路由,具体代码:

    import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

    const routes: RouteRecordRaw[] = []

    const modules = import.meta.globEager('./module/*.ts')

    for (const path in modules) {

      routes.push(...modules[path].default)

    }

    const router = createRouter({

      routes,

      history: createWebHashHistory()

    })

    export default router

    Vue3常用库

    vueuse

    git: https://github.com/vueuse/vueuse

    文档: https://vueuse.org/functions.html#category=Watch

    相关: https://juejin.cn/post/7030395303433863205

    less文档

    https://less.bootcss.com/#%E6%A6%82%E8%A7%88

    变量(Variables)

    命名规范,参考:

    https://github.com/ant-design/ant-design/blob/master/components/style/themes/default.less

    @ 10px;

    @height: @width + 10px;

    #header {

    @width;

    height: @height;

    }

    混合(Mixins)

    .bordered {

    border-top: dotted 1px black;

    border-bottom: solid 2px black;

    }

    #menu a { color: #111; .bordered(); }

    .post a { color: red; .bordered(); }

    嵌套(Nesting)

    .clearfix {

    display: block;

    zoom: 1;

    &:after {

    content: " ";

    display: block; font-size: 0;

    height: 0; clear: both;

    visibility: hidden;

    }

    }

    (& 表示当前选择器的父级)

    css module修改UI库样式

    使用:global

    ts文档

    Required / Readonly

    Required<T> 的作用就是将某个类型里的属性全部变为必选项。

    Readonly<T> 的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。

    Record

    Partial

    extends

    in

    typeof

    keyof

    Pick

    Exclude

    Extract

    Omit

    ReturnType

    设置全局ts类型

    Src文件夹添加typings.d.ts文件:

    上面为例子,src下面所有的tsx都可以这样使用CompTableAPI.ColumnsProps

    其他

    vscode设置代码片段

    window删除文件夹以及文件

    rd /s/q 文件夹

     

    npx和npm的区别

    npx 是 npm 的高级版本,npx 具有更强大的功能。

    1. 在项目中直接运行指令,直接运行node_modules中的某个指令,不需要输入文件路径
    1. 避免全局安装模块:npx 临时安装一个模块,使用过后删除这个模块(下面的两个模块不需要全局安装)

     

    1. 使用不同版本的命令,使用本地或者下载的命令

    一些优秀的博客

    如何面试

    前端如何面试: https://juejin.cn/post/6844903509502984206

     

    问1:

    有一块区域要展示一组数据,但数据需要请求 3 个接口才能计算得到,请问前端是怎么做的,如何优化,前端什么情况下可以放弃合并接口的要求。这个地方至少会考察到异步,本地缓存,延展下会问下并发,竞态,协程等。答得好不好完全在于你的知识面的深度和广度.

    问2:

    需要简历有故事性,比如项目背景,项目的内容,成果,你做了些什么。有没有相关的 paper 或是开源工程。简历中一定要体现出你的价值。如果没有,我一般会先问一个问题,在过去一年中你遇到的最大挑战是什么。其实这个问题很难回答,尤其是你自己在过去的工作中没有总结和思考的话。

    1. 是否有抽象。有很多问题本身都非常小,但是否能以点及面,考虑更大的层面。比如做不同项目,有没考虑体系建设,怎么考虑历史库的升级及维护;

    2. 是否有向前看。对新内容的判断,怎么使用也是考察的重点之一。尤其是为什么要用某个技术这个问题是我常问的。为了技术而技术,考虑问题的全面性就会差很多。

    继续探索的领域

    前端工程化

    前端微服务

    前端分布式架构

    低代码平台

    小程序

    Vue文档生成vuepress

    Github: https://github.com/vuejs/vuepress

     

    文档: https://vuepress.vuejs.org/zh/guide/

    React文档生成dumi

    文档: https://d.umijs.org/zh-CN/guide

    GitHub: https://github.com/umijs/dumi

     

  • 相关阅读:
    01_javaSE面试题:自增变量
    SpringBoot(二十)_404返回统一异常处理结果
    MD5加密工具代码
    SpringBoot(十九)_spring.profiles.active=@profiles.active@ 的使用
    读取本地文件转化成MultipartFile
    remote: http basic: access denied fatal: authentication failed for '‘解决办法
    git报错_you are not allowed to push code to protected branches on this project
    SpringBoot(十八)_springboot打成war包部署
    sql优化问题笔记(mysql)
    gitbook 简单使用
  • 原文地址:https://www.cnblogs.com/vs1435/p/16003970.html
Copyright © 2020-2023  润新知