React18 + Vite + TypeScript プロジェクトの体験概要

プロジェクトの作成

React + TypeScript テンプレートを選択します。React + TypeScript + SWC を選択しないでください。SWC は babel プラグインをサポートしていないため、es6 の構文と機能を変換できません。これについても後述します。

pnpm create vite

eslint サポートを追加

以下は、コレクションに追加できる、一般的に使用される eslint 構成とプラグインのリストです。github.com/dustinspeck…

設定方法

公式 Web サイトの手順に従って、次のコマンドを実行し、プロジェクトに応じて構成を選択するだけです。

pnpm create @eslint/config
复制代码

eslint でインデントをチェックしてみましょう

すべてのコードのインデックスを 2 つのスペースにしたい場合、そうしないとエラーが報告されます。どのように設定すればよいでしょうか?

.eslintrc.cjs ファイル内。

module.exports = {
  rules: {
    // 缩进必须为 2 个空格 https://eslint.org/docs/latest/rules/indent#rule-details
    "indent": ['error', 2],
    // 禁止所有 tab https://eslint.org/docs/latest/rules/no-tabs#rule-details
    'no-tabs': 'error',
  }
}
复制代码

タブを無効にするということは、インデントにタブの代わりにスペースを使用することを意味します。vscode の右下隅で構成を確認できます。

eslint-plugin-react-hooks プラグインを使用する

このプラグインは上のリストにあります。私たちが書いたreactフックが標準化されているかどうかを確認できます。

add eslint-plugin-react-hooks --dev
复制代码

次に、eslint 構成を拡張します

{
  "extends": [
    // ...
    "plugin:react-hooks/recommended"
  ]
}
复制代码

TypeScript のヒント

グローバル型を定義する方法

たとえば、次の型をグローバルに使用したいとします。

type pageview = 'pageview'

type click = 'click'

type blockview = 'blockview'

type elementview = 'elementview'
复制代码

global.d.ts ファイルで定義するだけで、グローバルに使用できます。ただし、global.d.ts ファイルが tsconfig.json ファイルの include オプションに含まれていることを確認してください。

declare global {
  declare type pageview = 'pageview'

  declare type click = 'click'

  declare type blockview = 'blockview'

  declare type elementview = 'elementview'
}
复制代码

配列型の要素型を取得する方法

type ListType = {a:number,b:string}[]
const list = [{}] as ListType

// 法一
type ArrayItem<T> = T extends Array<infer R> ? R : never

type Item = ArrayItem<ListType>


// 法二
TaskList['task'][number]

type Item = ListType[number]
复制代码

setState はコールバック関数のシナリオで渡されます

年齢を 42 歳とすると、このメソッドは setAge(age + 1) を 3 回呼び出します。

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}
复制代码

ただし、handleClick メソッドがトリガーされた後も、年齢は 45 歳ではなく 43 歳のままです。set メソッドは、現在実行中のコード内の age 状態変数を更新しないためです。したがって、setAge(age + 1) は毎回 setAge(43) になります。

この問題を解決するには、関数型パラメータを setAge に渡して次の状態を取得します。

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}
复制代码

a => a + 1 更新関数です。変更する状態を受け取り、関数本体で次の状態を計算して返します。

React は更新関数をキューに入れます。次に、次回のレンダリング時に、更新関数が同じ順序で呼び出されます。

  1. a => a + 1 は、変更される状態として 42 を受け取り、次の状態として 43 を返します。
  2. a => a + 1 は、変更する状態として 43 を受け取り、次の状態として 44 を返します。
  3. a => a + 1 は、変更する状態として 44 を受け取り、次の状態として 45 を返します。
  4. 他に更新するキューはなく、最終的に React は最終状態として 45 を保存します。

開発モードでは、React は更新関数が純粋で副作用がないことを確認するために 2 回呼び出します。

电影 あなたはRefにいます、あなたはMemoにいます、あなたはCallbackにいます

useRef は参照値を保存するために使用できます (再レンダリングの影響を受けません)。また、dom ノードを取得するためにも使用できます。

useMemo は値をキャッシュするために使用されます。依存関係が空の配列の場合、キャッシュされた値は決して変更されません。依存関係がある場合、依存関係が変更されると、再レンダリングのたびに関数が再実行され、新しい関数の戻り値がキャッシュされたデータとして使用されます。

useCallback は useMemo の構文糖であり、関数を返すのと同じです。

  const fn1 = useCallback(() => {
    console.log(123);
  }, [])

  const fn2 = useMemo(() => () => {
    console.log(123);
  }, [])
复制代码

React はコンポーネントの状態ソリューションをキャッシュしません

vue とは異なり、react はキープアライブを使用できます。

React が別のページにジャンプすると、前のページに戻ります。前のページが再レンダリングされます。

レベルが限られているため、インターフェイス データをキャッシュすることが決定されました。初回はキャッシュは要求されません。2 回目のメソッド実行時にはキャッシュがあるかどうかが判断され、キャッシュされている場合はキャッシュされたデータが直接返されます。

このフックを理解するには、モジュールが 1 回だけロードされることを理解する必要があると思います。また、再レンダリングするたびに、一部のメソッドが再実行され、一部のメソッドが再定義されることにも注意してください。

useCacheフック

interface Cb {
  (...arg: unknown[]): unknown
}
const cacheMap = new Map();

export default (key: string, callback: Cb) => {
  return async (cache = true) => {
    const result = cacheMap.get(key)

    if(cache && result) {
      return result
    }

    const res = await callback()
    cacheMap.set(key, res)

    return res
  }
}
复制代码

使用

function App () {  

  const [count, setCount] = useState(0)

  useEffect(() => {
    fn()
  })

  async function fn () {
    const res = await getStatus() 
    console.log('===>', res);
  }

  const getStatus = useCache('getStatus', () => {
    const arr = [1,2,3,4]
    const res = [] as number[]
    arr.forEach(num => {
      console.log(num);
      res.push(num)
    });

    return res
  })

  function add () {
    setCount(c => c + 1)
  }

  return (
    <>
      <h2>==</h2>
      <br />
      {count}
      <br />
      <button onClick={add}>add</button>
      <h2>==</h2>
    </>
  )
}
复制代码

反応ルーターダムは使いやすいです

ページジャンプ管理は、react-router-dom を使用します

依存関係パッケージをダウンロードする

pnpm install react-router-dom
复制代码

ルーティング テーブルと遅延ロード コンポーネントを作成する

/src/ルーター/index.tsx

import React, {lazy} from 'react'
import { createHashRouter, Navigate } from 'react-router-dom'
import HomeC from '../pages/home'

// 引入方法一
// const Home = lazy(() => import('../pages/home'))

// 引入方法二
// const Home = lazy(async () => {
//   const res = await new Promise<any>((resolve) => {
//     setTimeout(() => {
//       resolve(HomeC)
//     }, 2000)
//   })
//   return {default: res}
// })

// 引入方法三
const Home = lazy(async () => {
  return new Promise<any>((resolve) => {
    setTimeout(() => {
      resolve({
        default: HomeC
      })
    }, 2000)
  })
})

const Detail = lazy(() => import('../pages/detail'))
const Record = lazy(() => import('../pages/record'))

export default createHashRouter([
  {
    path: '/',
    element: <Navigate replace to="/home" />
  },
  {
    path: '/home',
    element: <Home />
  },
  {
    path: '/detail',
    element: <Detail />
  },
  {
    path: '/record',
    element: <Record />
  }
])

复制代码

ルートコンポーネントを変更する

App.tsx

import React, { Suspense } from 'react'
import { RouterProvider } from 'react-router-dom';
import Loading from './pages/loading'
import router from './router'
import './App.css'

function App () {
  return (
    <Suspense fallback={<Loading />}>
      <RouterProvider router={router}></RouterProvider>
    </Suspense>
  )
}

export default App
复制代码

ルートジャンプ

ホームページ

import React from 'react';
import { useNavigate } from 'react-router-dom'

const Index =  () => {
  // 路由跳转
  const router = useNavigate()

  function toDetail () {
    router('/detail')
  }

  function toRecord () {
    router('/record')
  }

  return <>
    <div>home 页</div>
    <br />
    <button onClick={toDetail}>去 detail</button>
    <br />
    <br />
    <button onClick={toRecord}>去 record</button>
  </>
}

export default Index
复制代码

コンポーネントの props タイプを定義し、デフォルト値を宣言します

通常の状態

デフォルトPropsを使用します。

import React, { memo } from "react"

interface Props {
  name?: string
}

// function Index(props: Props){
//   return (
//     <button>name: {props.name}</button>
//   )
// }

const Index = (props: Props) => {
  return (
    <button>name: {props.name}</button>
  )
}

// const 的作用域不会提升
Index.defaultProps = {
  name: '小王'
} as Props

export default memo(Index)
复制代码

複雑さ: forwardRef が使用される場合

forwardRef は、子コンポーネント内の dom 要素を取得するために使用されます。

forwardRef を使用する場合、デフォルト値の定義は次のように記述する必要があります。

import React, {forwardRef} from 'react'

interface Props {
  btnClick?: (item: any) => void
  totalScore?: number
}

const Index = forwardRef(function HeadReward(props: Props, ref: any) {
    // ...
})

Index.defaultProps = {
  btnClick: () => {
    //
  },
  totalScore: 0
} as Props

export default Index
复制代码

React のパフォーマンス最適化に関する考慮事項

すべての機能コンポーネントが使用されるため、setState が使用されるたびに再レンダリングが発生します。

React では、親コンポーネントが更新されると、それに応じて子コンポーネントも更新されるため、ある程度の最適化が必要です。

1 サブコンポーネントはメモを使用してラップされます

親コンポーネント

function App() {
  const [count, setCount] = useState(0)

  return (
    <>  
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>add</button>
      <Foo />
    </>
  )
}
复制代码

サブアセンブリ

import { memo } from "react"

const Index = () => {
  console.log('re-render');
  
  return <div>子组件</div>
}

export default memo(Index)
复制代码

2. useCallback を使用して、子コンポーネントに渡される関数をラップします。

子コンポーネントに渡さない場合は、useCallback ラッパーを使用できません。再レンダリング時にのみ再定義されるため、この関数は内部的には実行されません。

なぜ useCallback を使用してラップするのでしょうか? 再レンダリングの前後で作成された 2 つの関数の参照アドレスが同じではなく、それらが同じかどうかを比較するときに Object.is が false を返すためです。

親コンポーネント

import { useCallback, useState } from 'react'
import Foo from '@/components/foo'
import './App.css'

function App() {
  const request = useCallback(() => {
    setTimeout(() => {
      console.log('请求');
    }, 10)
  }, [])

  // const request = () => {
  //   setTimeout(() => {
  //     console.log('请求');
  //   }, 10)
  // }

  const [count, setCount] = useState(0)

  return (
    <> 
      <button onClick={()=>setCount(count + 1)}>{count}</button>
      <Foo request={request} />
    </>
  )
}

export default App
复制代码

サブアセンブリ

import { memo } from "react"

interface Props {
  request: () => void
}

const Index = (props: Props) => {
  console.log('re-render');
  
  return (
    <button onClick={props.request}>按钮</button>
  )
}

export default memo(Index)
复制代码

3 サブコンポーネントに渡される参照データ型は useMemo でラップする必要があります。実際には、前述したコンポーネントに関数を渡すときと同じで、関数を useCallback でラップする必要があります。関数は参照データ型でもあり、特別なオブジェクトです。

親コンポーネント

import { useCallback, useMemo, useState } from 'react'
import Foo from '@/components/foo'
import './App.css'

function App() {
  const obj = useMemo(() => ({
    name: 'xiaowang',
    age: 19
  }), [])

  const [count, setCount] = useState(0)

  return (
    <> 
      <button onClick={()=>setCount(count + 1)}>{count}</button>
      <Foo obj={obj}/>
    </>
  )
}

export default App
复制代码

サブアセンブリ

import { memo } from "react"

interface Props {
  obj: {
    name: string,
    age: number
  }
}
const Index = (props: Props) => {
  console.log('re-render');
  
  return (
    <button>按钮</button>
  )
}

export default memo(Index)
复制代码

useMemoを使用して4つのフックがラップされています

フックで useMemo パッケージを使用するのはなぜですか? なぜなら、フックを使用するときは、基本的に計算を実行する関数を呼び出していることになるからです。

再レンダリングするたびにフック関数を実行すると、パフォーマンスが低下する可能性があります。そこで useMemo を使って関数実行の計算結果をキャッシュすることができます。

コンポーネント

function App() {
  const list = usePow([1,2,3,4])
  console.log(list);

  const list2 = usePow([1,2])
  console.log(list2);
  
  
  const [count, setCount] = useState(0)

  return (
    <> 
      <button onClick={()=>setCount(count + 1)}>{count}</button>
      <Foo />
    </>
  )
}
复制代码

import { useMemo } from 'react';

export default (list: number[]) => {
  return useMemo(() => list.map(num => {
    console.log('hook 执行了');
    return Math.pow(num, 2)
  }), [])
}
复制代码

機能コンポーネントでの ref の使用

現在のコンポーネントの dom ノードを取得します

import { useRef, useEffect, useState } from 'react'
import Foo from '@/components/foo'
import './App.css'

function App() {  
  const button = useRef(null)

  useEffect(() => {
    console.log(button.current);
  }, [])

  return (
    <>
      <h2>==</h2>
      <button ref={button} >按钮</button>
      <h2>==</h2>
    </>
  )
}

export default App

复制代码

子コンポーネントのdomノードを取得します。

転送するには、forwardRef を使用します。

親コンポーネント

import { useRef, useEffect } from 'react'
import Foo from '@/components/foo'
import './App.css'

function App() {  
  const button = useRef(null)

  useEffect(() => {
    console.log(button.current);
  }, [])

  return (
    <>
      <h2>==</h2>
      <Foo ref={button} />
      <h2>==</h2>
    </>
  )
}

export default App
复制代码

サブアセンブリ

import React, { forwardRef } from "react"

interface Props {
  name?: string
}

const Index = (props: Props, ref: any) => {
  return (
    <button ref={ref}>name: {props.name}</button>
  )
}

export default forwardRef(Index)
复制代码

memo は forwardRef と一緒に使用されます

サブコンポーネントの不必要な再レンダリングを防ぐために、メモを使用して forwardRef で転送されたコンポーネントをラップします。

import React, { forwardRef, memo } from "react"

interface Props {
  name?: string
}

const Index = (props: Props, ref: any) => {
  // console.log(123);
  
  return (
    <button ref={ref}>name: {props.name}</button>
  )
}

export default memo(forwardRef(Index))
复制代码

memoはforwardRefおよびdefaultPropsとともに使用されます

forwardRef 転送後に返されるコンポーネントに、defaultProps 属性を追加します。そのため、コンポーネントが親コンポーネントから prop を受け取らない場合は、デフォルト値が使用されます。

defaultProps 属性は、forwardRef 転送後に返されるコンポーネントにのみ追加できることに注意してください。追加しない場合は、エラーが報告されます。

import React, { forwardRef, memo } from "react"

interface Props {
  name?: string
}

const Index = forwardRef((props: Props, ref: any) => {
  
  return (
    <button ref={ref}>name: {props.name}</button>
  )
})
// 注意这里只能在 forwardRef 转发后返回的组件中添加 defaultProps 属性
// 否则会报错。
Index.defaultProps = {
  name: '小王'
} as Props

export default memo(Index)
复制代码

循環リストの dom ノードを取得します

リストは現在のコンポーネント内にあります

関数をrefに渡します

import { useRef, useEffect, useState } from 'react'
import Foo from '@/components/foo'
import './App.css'

function App() {  
 const buttonList = useRef<Element[]>([])

  function getRef(dom: Element | null) {
    if(!dom) return

    buttonList.current.push(dom)
  }
  return (
    <>
      {
      [1,2,3,4,5].map((num) => {
        return <button ref={getRef} key={num}>name: {num}</button>
      })
      }
    </>
  )
}

export default App

复制代码

子コンポーネント内のリスト

親コンポーネント

import { useRef, useEffect, useState } from 'react'
import Foo from '@/components/foo'
import './App.css'

function App() {  
  const buttonList = useRef(null)

  useEffect(() => {
    console.log(buttonList.current);
  }, [])

  return (
    <>
      <h2>==</h2>
      <Foo ref={buttonList} />
      <h2>==</h2>
    </>
  )
}

export default App
复制代码

子コンポーネントは、useImperativeHandle カスタマイズを通じてプロパティ、メソッドなどを親コンポーネントに公開します。

import React, { forwardRef, memo, useRef, useImperativeHandle } from "react"

interface Props {
  name?: string
}

const Index = forwardRef((props: Props, ref: any) => {

  const buttonList = [] as (Element | null)[]

  function getRef(dom: Element | null) {
    if(!dom) return

    buttonList.push(dom)
  }

  useImperativeHandle(ref, () => ({
    buttonList
  }))

  return (
    <>
      {
      [1,2,3,4,5].map((item) => {
        return <button ref={getRef} key={item}>name: {props.name}</button>
      })
      }
    </>
  )
})

Index.defaultProps = {
  name: '小王'
} as Props

export default memo(Index)
复制代码

ユーザーが手動で画面をズームできないようにする

以下のスクリプトをindex.htmlに追加します。

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
复制代码

反応スタイルの分離

命名規則については合意することができますが、各ページまたはコンポーネントのルート タグの CSS クラス名を同じにすることはできません。

しかし、vue のようなスコープ付き属性をタグに追加して、スタイル分離の効果を実現するにはどうすればよいでしょうか <style>? styled-jsxライブラリを使用できます  。

以下にその使い方を簡単に紹介します。

依存関係をダウンロードする

pnpm install --save styled-jsx
复制代码

次に、babel 設定ファイルで設定します plugins

{
  "plugins": [
+   "styled-jsx/babel"
  ],
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ],
  "targets": "chrome 70"
}
复制代码

そして使用します

const Index =  () => {
  // 路由跳转
  const router = useNavigate()
  
  function back () {
    router(-1)
  }
  return (
    <>
      <div className='abc'>record 页</div>
      <button onClick={back}>返回</button>
      <style jsx>{`
        .abc {
          color: red;
        }
      `}</style>
    </>
  )
}
复制代码

または、スタイルをインラインに配置する代わりに、別の CSS ファイルを使用します。

// 导入 css(?raw 是将文件内容以字符串的形式引入)
// https://vitejs.dev/guide/assets.html#importing-asset-as-string
import styled from  './index.scss?raw'

const Index =  () => {
  // 路由跳转
  const router = useNavigate()
  
  function back () {
    router(-1)
  }
  return (
    <>
      <div className='abc'>record 页</div>
      <button className='back' onClick={back}>返回</button>
      <div id="div">
        <div className='btn'></div>
      </div>
      <style jsx>{styled}</style>
    </>
  )
}

export default Index  
复制代码

適応的なページ幅を実現するために px を rem に変換します

詳細については、ここをクリックしてください。設定方法だけは知っていても、postcss-pxtorem の rootValue 設定項目が何を意味するのか理解していない人も多いと思います。

amfe-flexible と postcss を使用して px を rem に変換します。ui ライブラリ antd-mobile を使用します。

pnpm add postcss-pxtorem postcss amfe-flexible -S
复制代码

エントリーファイルでインポート

// 可伸缩布局库
import 'amfe-flexible'
复制代码

ルートディレクトリにpostcss.config.cjsを作成します。

// eslint-disable-next-line no-undef
module.exports = {
  plugins: {
    'postcss-pxtorem': {
      rootValue: 37.5, // 设计稿元素尺寸/10
      unitPrecision: 5, 
      propList: ['*'], // 是一个存储哪些将被转换的属性列表,这里设置为['*']全部,假设需要仅对边框进行设置,可以写['*', '!border*']
      selectorBlackList: [], // 则是一个对css选择器进行过滤的数组,比如你设置为['el-'],那所有el-类名里面有关px的样式将不被转换,这里也支持正则写法。
      replace: true,
      mediaQuery: false, // 媒体查询( @media screen 之类的)中不生效
      minPixelValue: 0, // px 绝对值小于 0 的不会被转换
      exclude: /node_modules/i,
    },
  },
}

复制代码

グリッドレイアウトの適用

次のスタイルを実装します。

コード:

  <div className='week'>
    {[1,2,3,4,5,6,7].map((item, index) => (
      <div ref={getRef} className={`day day${index + 1}`} key={`${item}`} onClick={() => gather(index)}>
        <div className='day-num'>第{item}天</div>
        <div className='top'>
          <img className='img' src={star} />
        </div>
        <div className='bottom'>5积分</div>
      </div>
    ))}
  </div>
复制代码

キーCSSは次のとおりです

  & > .week {
    display: grid;
    grid-template-columns: repeat(4, auto);
    grid-template-rows: repeat(2, 84px);
    grid-row-gap: 8px;
    grid-column-gap: 8px;
    margin-top: 14px;
    & > .day {  
        // ...
    }
    > .day7 {
      grid-column-start: span 2
    }
  }
复制代码

ポイントを集めるアニメーション

アイデア:

1 アニメーションの宛先ノードを取得し、7 要素のノードを配列に入れます。

2 ある日をクリックすると、現在クリックされている要素ノードの情報を取得し、アニメーション開始位置を初期化し、目的の位置に到達するようにアニメーションを設定します。

これはどのようにして達成されるのでしょうか?

1 アニメーション先のdomノードを取得する

アニメーション終了位置のdomノード情報を取得します。

const destinationDom = useRef<HTMLDivElement>(null)
复制代码
<div ref={destinationDom} className='destination'></div>
复制代码

2 7 つの要素を持つノードを取得する

const refList = [] as HTMLDivElement[]

function getRef (dom: HTMLDivElement | null) {
    if(dom) refList.push(dom)
}
复制代码
      <div className='week'>
        {[1,2,3,4,5,6,7].map((item, index) => (
          <div ref={getRef} className={`day day${index + 1}`} key={`${item}`} onClick={() => gather(index)}>
            <div className='day-num'>第{item}天</div>
            <div className='top'>
              <img className='img' src={star} />
            </div>
            <div className='bottom'>5积分</div>
          </div>
        ))}
      </div>
复制代码

3 アニメーション要素のCSSを設定する

書いているときに、immer を使用してコードを最適化し、よりエレガントにできないか考えていました。そうしないと、setState が使用されるたびに、この大きなオブジェクトを書き込む必要があります。興味のある学生はぜひ試してみてください。

  const [transitionStyle, setTransitionStyle] = useState({
    display: 'none',
    top: 0,
    left: 0,
    transform: 'scale(0)',
    transition: 'none'
  })

  const canGather = useRef(true)

  function gather (index: number) {
    if(!canGather.current) return
    canGather.current = false

    const target = refList[index].getBoundingClientRect()
    const destination = destinationDom.current!.getBoundingClientRect()
    
    setTransitionStyle({
      display: 'block',
      top: target.top,
      left: target.left,
      transform: 'scale(1)',
      transition: 'none'
    })

    setTimeout(() => {
      setTransitionStyle({
        display: 'block',
        top: destination.top,
        left: destination.left,
        transform: 'scale(0.5)',
        transition: 'all .6s linear'
      })
    },0)
    setTimeout(() => {
      setTransitionStyle({
        display: 'none',
        top: 0,
        left: 0,
        transform: 'scale(1)',
        transition: 'none'
      })
      canGather.current = true
    }, 600)
  }
复制代码

tsx

  <div ref={destinationDom} className='destination'>目的地</div>
  

  {/* 注意样式,position: fixed */}
  <div className='transition-div' style={transitionStyle}></div>
复制代码

モジュールは一度だけロードされます

モジュール ファイルが異なればスコープも異なり、実行されるのは 1 回だけです。たとえば、次のログは 1 回だけ出力されます。useRef を他のファイルに何度インポートしても、ログは出力されなくなります。

let value

console.log(1)

export function useRef(_value) {
    if(value) {
        return value
    }
    
    value = _value
    
    return value
}
复制代码

コンポーネントライブラリ

Ant Design モバイルを使用します。しかし、正直に言うと、私が使用したのはトースト プロンプトやポップアップ ボックスなどのコンポーネントのほんの一部だけで、他の多くのスタイルやビジネス コンポーネントは自分で記述する必要がありました。

アイコン

追加のサードパーティのアイコン ライブラリを導入することなく、ui を使用してカットされた画像を直接ダウンロードします。

ステータス管理

mobx の依存関係がインストールされていますが、ほとんど使用されていません。

プロジェクトはそれほど複雑ではなく、数ページだけなので。UseState はさらに使用されます。ただし、学習用であれば、ページデータを強制的に mobx で管理することができます。

環境変数とパターン

詳細については、ドキュメントを参照してください。  cn.vitejs.dev/guide/env-a…

このモードは、コマンドラインで --mode オプションを使用してオーバーライドできます。

  "scripts": {
    "dev:test": "vite --mode test",
    "dev:pre": "vite --mode pre",
    "dev:prod": "vite --mode prod",
    "build:test": "tsc && vite build --mode test",
    "build:pre": "tsc && vite build --mode pre",
    "build:prod": "tsc && vite build --mode prod"
  },
复制代码

tsc: TypeScript をコンパイルする

vite は、dotenv を通じてプロジェクト内の次のファイルを読み取り、追加の環境変数を追加します。

# 必须以 VITE_ 开头,否则为 undefined

# .env.test
VITE_SHAREURL='https://test.share.com.cn'
VITE_BASEURL='https://www.test.baidu.com'

# .env.pre
VITE_SHAREURL='https://pre.share.com.cn'
VITE_BASEURL='https://www.pre.baidu.com'

# .env.prod
VITE_SHAREURL='https://prod.share.com.cn'
VITE_BASEURL='https://www.prod.baidu.com'
复制代码

このようにして、環境に応じて操作を選択できます。たとえば、オンライン デバッグ機能は開発モードでのみ導入されます。

if (import.meta.env.MODE === 'test') {
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore
  import('https://cdn.staticfile.org/vConsole/3.3.4/vconsole.min.js').then(() => {
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    new window.VConsole();
  })
}
复制代码

運用環境でログとデバッガを削除します。

defineConfig は関数で渡すことができ、mode の値は config パラメーターを通じて取得できます。

cn.vitejs.dev/config/#con…


export default defineConfig((config) => {
  return {
    build: {
      minify: 'terser',
      terserOptions: {
        compress: {
          // 默认是 false。移除 console
          drop_console: config.mode === 'prod',
          // 默认是 true。移除 debugger
          drop_debugger: config.mode === 'prod',
        },
      }
    }
  }
})
复制代码

パッケージ化のために gzip 圧縮を有効にする

Gzip 圧縮により、サーバーのネットワーク帯域幅を大幅に節約できます。

gzipとは何ですか? 私が理解しているのは、コンテンツを圧縮できるツールです。ブラウザが URL をリクエストするときに、属性 accept-encoding: gzip がリクエスト ヘッダーに設定されている場合、ブラウザが gzip 圧縮をサポートしていることを示します。

ブラウザーから送信されたリクエストを受信した後、サーバーはブラウザーが gzip をサポートしているかどうかを判断します。gzip がサポートされている場合は、gzip 圧縮されたコンテンツがブラウザに送信され、gzip がサポートされていない場合は、非圧縮のコンテンツがブラウザに送信されます。一般に、ブラウザとサーバーの両方が gzip をサポートします。

ブラウザはサーバーからの応答を受信後、コンテンツが圧縮されているかどうかを判断し、圧縮されている場合には解凍してページのコンテンツを表示します。

vite プロジェクトで gzip 圧縮を有効にするにはどうすればよいですか? まずプラグインをダウンロードします。

pnpm add rollup-plugin-gzip --save-dev
复制代码

vite.config.ts内

import { defineConfig } from 'vite'
import gzipPlugin from 'rollup-plugin-gzip'

export default defineConfig({
  base: './',
  build: {
    rollupOptions: {
      plugins: [gzipPlugin()]
    }
  }
})
复制代码

次に、パッケージ化すると、追加の .gz ファイルが見つかり、gzip 圧縮が正常に有効になったことを示します。

インターフェースリクエスト

同社がパッケージ化したリクエストツールを利用しているが、社内でどのように実装されているかは不明。

データ暗号化

気にしなくてもいいのに。しかし調べてみると、jsファイルを暗号化するツールという方法が紹介されているようで、暗号化の方法はいくつかあります。その後、インターフェイスがリクエストを行うと、使用する暗号化方式が決定され、送信されるパラメータが暗号化されます。

発生した問題と解決策を記録する

「Foo」は JSX コンポーネントとして使用できません。

これは実際には TypeScript のバージョンの問題です。

Vscodeを使用してTSを書き込むと、奇妙な赤みが表示されます。TypeScriptのバージョンに問題がある可能性があります。Vscode の TS バージョンの代わりに、作業ディレクトリ内の TS バージョンを使用することを選択します。

ショートカットキー ctrl + shift + p

次に、ワークスペース バージョンを使用することを選択します 

注: バージョンを設定した後も、vscode で ts が赤く点滅している可能性があります。依存関係と package-lock.json ファイルの両方を削除し、依存関係を再度ダウンロードしてみてください。

型注釈は TypeScript ファイル (.tsx ファイル) でのみ使用できます。

この問題は、Vscode の言語モードに問題があるために発生します。言語モードを再選択して再起動してください。

エラー「globalThis は定義されていません」は、低バージョンの Android ブラウザで報告されます。

このプロジェクトは、Android ブラウザの以前のバージョンでこのエラーを報告しますが、次のスクリプトをindex.html に追加することで解決できます。

    <script>
      this.globalThis || (this.globalThis = this);
    </script>
复制代码

開発中、以前のバージョンのブラウザは es6 の新しい構文の問題を認識しません

前提条件: vite でプロジェクトを作成する場合は、SWC を選択しないでください。SWC は次の構成をサポートしていません。

Vue プロジェクトについては、  www.cnblogs.com/ygunoil /p/1… を参照してください。

babel プラグインは vite.config.ts で設定できます。

  plugins: [react({
   babel: {
     plugins: [
       "@babel/plugin-proposal-optional-chaining",
       "@babel/plugin-proposal-nullish-coalescing-operator"
     ]
   }
 })],
复制代码

ルート ディレクトリの .babelrc で直接設定します。プラグインを 1 つずつ設定する必要を避けるために、babel のデフォルトの preset-env を使用します。

plugins: [
 react({
   babel: {
       // 表示使用根目录下 .babelrc 文件的 babel 配置
       babelrc: true,
   },
 })
],
复制代码

.babelrc

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ]
  ],
  "targets": "chrome 70"
}
复制代码

設定後、vite はオプションのチェーンや null 値マージ演算子などの es6 の新しい構文を使用できるようになります。

エイリアスを構成するときに、パスが見つからないというメッセージが表示される

ts + vite プロジェクトでは、「パス」が見つからないというメッセージが表示されます。

理由分析: path モジュールは、node.js の組み込み関数ですが、node.js 自体は typescript をサポートしていないため、typescript プロジェクトで直接使用することはできません。

解決

npm install @types/node --save-dev
复制代码

これにより、エイリアスの構成が簡単になります。そのパスは絶対パスでなければならないためです。

export default defineConfig({ 
  resolve: {
    alias: {
      // 只能是绝对路径
      // '@': fileURLToPath(new URL('./src', import.meta.url)),
      '@': path.resolve('./src')
    },
  },
})
复制代码

パッケージ化されたコードが下位バージョンのブラウザをサポートできるようにする

vite の開発とパッケージ化の構築ロジックは異なります。デフォルトのパッケージ化ターゲットは、ネイティブ ESM スクリプト タグネイティブ ESM 動的インポート 、および import.meta をサポートするブラウザです。

  • クロム >=87
  • Firefox >=78
  • サファリ >=14
  • エッジ >=88

パッケージ化されたコードで従来のブラウザをサポートするには、レガシー プラグインを使用できます。それ以外の場合、コードでオプションのチェーンやコントロールのマージ演算子などの新しい es6 構文機能が使用されている場合、ブラウザーの下位バージョンをパッケージ化して実行するとエラーが報告されます。

pnpm add -D @vitejs/plugin-legacy
复制代码

vite.config.ts

plugins: [
  legacy({
    targets: ['> 0.25%', 'last 2 versions and not dead'],
  }),
],
复制代码

コードを超えた何か

よく使用されるスクリプトコマンド

より多くの操作のロックを解除するには、vscode のターミナルを git bash に変更することをお勧めします。

スクリプトコマンドをいくつか覚えた理由は、手動でファイルを別の場所にコピー&ペーストするのが面倒だからです。コマンドラインを使用した方が簡単で速いと思います。

たとえば、パッケージ化された dist ディレクトリをデスクトップに移動したい場合は、次のコマンドを実行します。

ここで、desktop_path は私が設定したコンピューターのグローバル環境変数であり、コンピューターのデスクトップのパスです。

mv dist ${desktop_path}/
复制代码

以下は、より便利だと思われるスクリプト コマンドのリストです。

# 在当前目录下,把 文件 a 及以下文件移动到 b 目录下(改名也可以用这个命令)
mv a b

# 创建一个文件夹
mkdir test

# 创建一个文件
touch hello.md

# 查看当前目录下所有东西
ls

# 删除文件或文件夹
rm 文件名

# 删除文件及以下的文件
rm -rf xxx

-r 就是向下递归,不管有多少级目录,一并删除
-f 就是直接强行删除,不作任何提示的意思

# 查看 shell 全局变量
env

# 输出全局变量
echo $HOME or $HOME

# 查看当前文件目录
pwd

# 清空命令行窗口
clear

# printenv 查看某个环境变量,变量前不用加 $ 符号了
printenv HOME 

# echo 查看某个环境变量,变量前要加 $ 符号
echo $HOME 或者 echo ${HOME}
复制代码

git bash では、ローカル変数を設定してもグローバル変数を設定しても、シェルを終了すると無効になるため、コンピュータに環境変数を手動で直接追加できます。

httpサービスを素早く開始する

# 下载
npm i http-server -g

# 起服务
http-server
复制代码

コンピュータを使用して携帯電話上の Web ページをワイヤレスでデバッグする

自分が作成したページをモバイル ブラウザで開いたときにどのように見えるかを知りたい場合があります。

コンピュータと携帯電話を同じ Wi-Fi に接続できます (コーヒー ショップの Wi-Fi が利用できない場合があります。ファイアウォールの問題である可能性があります)。コンピュータで開発サーバーを起動した後、モバイル ブラウザに IP アドレスを入力して、プレビュー。

USB データ ケーブルを使用してコンピュータに接続し、携帯電話上のページをコンピュータでデバッグすることもできます。印刷ログやページ要素、リクエスト送信などを見ることができて便利です。

しかし、データ ケーブルを使用するのが面倒に感じることがあります。コンピュータを使用して、携帯電話上の Web ページをワイヤレスでデバッグできますか? 許可されています!

adb (Android Development Bridage) を使用する必要があります。

詳細な手順

  1. コンピューターと携帯電話は同じネットワークに接続されており、携帯電話はワイヤレス デバッグをオンにします。
  2. 最新バージョンの Android Debug Bridge (adb) をダウンロードします 。developer.android.google.cn/studio /rel…
  3. ダウンロードして解凍したら、ディレクトリアドレスに「cmd」と入力してコマンドラインウィンドウを開きます。 

  4. 携帯電話でワイヤレス デバッグ オプションをクリックし、[ペアリング コードを使用してデバイスをペアリングする] をクリックします。

  1. コンピューターのコマンド ラインに上の図を入力します。これは、ペアリング コードのポップアップ ボックスの IP アドレスとポート番号です。次に、プロンプトに従ってペアリング コード (736001) を入力します。

    adb pair ip:port
    复制代码
  2. ペアリングが成功したら、コマンドを入力します (ここでの ip:port は前の手順とは異なることに注意してください)

    adb connect ip:port
    复制代码

  3. リンクされたデバイスを表示する

    adb devices -l 查看链接的设备
    复制代码
  4. Google または Edge でデバッグ ページを開いてデバッグします。(デバイスが見つかるまでに時間がかかる場合があります)

chrome://inspect/#devices

Edge://inspect/#devices

携帯電話で Edge または Google、またはアプリに埋め込まれた Web ページを開き、コンピューターが検索するまで待ちます。

 

ワイヤレスデバッグの概要

1、首先下载 adb。
2、手机电脑连在同一 wifi 下
3、手机启用开发者模式,打开无线调试,点击使用配对码配对设备
4、adb pair ip:port。(手机上显示的 ip:port)
5、adb devices -l 查看链接的设备
6、adb connect ip:port
复制代码

使ってみるとまだまだ制約が多いです。

まず、Edge を使用できるのは、携帯電話の Edge ブラウザで開かれた Web ページのデバッグのみです。Google は、Edge または携帯電話上の Google で開いた Web ページをデバッグできます。

Google を使用してデバッグする場合、モバイル ブラウザのバージョンが低すぎると、検査ボタンをクリックした後に HTTP/1.1 404 Not Found 問題が発生します。デバッグしたい場合は、コンピュータのブラウザのバージョンを下げるだけです。

おすすめ

転載: blog.csdn.net/qq_21473443/article/details/130484285