当前找工作环境恶劣,很多求职者表示招聘不仅要求会 vue,还要求会 react。
刚好这段时间我用 React18 + Vite + TS 为公司从 0 到 1 开发了一个项目,就顺便总结了一些前端开发知识和技巧,帮助我记忆这些知识的同时,希望能对你也有所启发。欢迎评论区交流。
我会把代码(当然不是真实项目的。。)上传到 GitHub 上,文末可以取链接。最后还请多多评论、点赞支持!废话不多说,下面开始进入正题:
选择 React + TypeScript 模板。不要选用 React + TypeScript + SWC。因为 SWC 不支持 babel 插件,不能转换 es6 语法和特性。这个后面也会说到。
pnpm create vite复制代码
image.png
这里有一个 eslint 常用配置、插件的清单,可以收藏看看。github.com/dustinspeck…[1]
根据官网的说明,只需要简单的执行下面的命令,然后根据你的项目进行选择配置。
pnpm create @eslint/config复制代码
image.png
如果我想让所有代码的索引为 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', }}复制代码
禁用 tab 表示缩进使用 spaces,而不是 tab。可以在 vscode 右下角查看配置。
image.png
这个插件是在上面那个清单里看到的。可以检查我们写的 react hook 是否规范。
add eslint-plugin-react-hooks --dev复制代码
然后扩展 eslint 配置
{ "extends": [ // ... "plugin:react-hooks/recommended" ]}复制代码
比如我想在全局都能使用以下几个类型
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 : nevertype Item = ArrayItem<ListType>// 法二TaskList['task'][number]type Item = ListType[number]复制代码
假设 age 为 42,这个方法将会调用 setAge(age + 1) 三次。
function handleClick() { setAge(age + 1); // setAge(42 + 1) setAge(age + 1); // setAge(42 + 1) setAge(age + 1); // setAge(42 + 1)}复制代码
然而,触发 handleClick 方法后,age 还是 43,不是 45。因为 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
是你的更新函数。它接收一个待改变的 state,然后在函数体计算返回下一个 state。
React 把更新函数放入一个队列中。然后在下一次 render 时,更新函数将会以相同的顺序被调用。
在开发模式下,React 可能会调用两次你的更新函数,目的是确保他们是纯的没有副作用。
useRef 可用来存储一个引用值(不会受 re-render 影响),也可用来获取 dom 节点。
useMemo 用来缓存一个值。当依赖项为空数组时,缓存值永远不会变。当有依赖性时,每次 re-render 如果依赖改变,那么将重新执行函数,将新的函数返回值作为缓存的数据。
useCallback 是 useMemo 的语法糖,相当于返回一个函数。
const fn1 = useCallback(() => { console.log(123); }, []) const fn2 = useMemo(() => () => { console.log(123); }, [])复制代码
react 不像 vue 能使用 keep-alive。
当 react 跳到另外一个页面,再返回到上一个页面。上一个页面会重新渲染。
由于水平有限,所以决定将接口数据进行缓存。第一次没有缓存会请求。第二次执行方法时判断是否有缓存,有就直接返回缓存的数据。
我觉得要理解这个 hook,要明白模块只会被加载一次。还要明白每次 re-render 有些方法是会被重新执行,有些方法是会被重新定义。
useCache hook
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
2023-03-01-17-13-37.gif
下载依赖包
pnpm install react-router-dom复制代码
/src/router/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复制代码
Home 页
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复制代码
使用 defaultProps。
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 Propsexport default memo(Index)复制代码
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 Propsexport default Index复制代码
由于全部使用的函数式组件,每次 setState 的时候,都会导致 re-render。
而 React 是父组件更新,子组件也会跟着更新,所以需要做一些优化。
父组件
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)复制代码
如果不传递给子组件,可以不使用 useCallback 包裹。因为 re-render 时只是重新定义了一遍,函数内部并没有执行。
为什么要用 useCallback 包裹,因为 re-render 时前后创建的两个函数引用地址并不一样,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)复制代码
父组件
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)复制代码
为什么 hook 要使用 useMemo 包裹?因为我们使用 hook 本质上是在调用一个函数执行的计算结果。
每次 re-render 的时候,都去执行一下 hook 函数的话,可能会造成性能上的损失。所以可以使用 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 /> </> )}复制代码
hook
import { useMemo } from 'react';export default (list: number[]) => { return useMemo(() => list.map(num => { console.log('hook 执行了'); return Math.pow(num, 2) }), [])}复制代码
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复制代码
使用 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 转发后的组件,防止子组件不必要的 re-render。
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))复制代码
在 forwardRef 转发后返回的组件中添加 defaultProps 属性。以便当组件没有接收到父组件传来的 prop,使用默认值。
注意这里只能在 forwardRef 转发后返回的组件中添加 defaultProps 属性,否则会报错。
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 Propsexport default memo(Index)复制代码
给 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 Propsexport default memo(Index)复制代码
在 index.html 添加如下脚本
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />复制代码
目前还没有一个感觉很好的解决方案。所以只能约定命名规范,每个页面或组件的根标签的 css 类名都不能一样。
点击这里了解更多!我猜很多人只是会配置,但不明白 postcss-pxtorem 中的 rootValue 配置项是什么意思[2]
使用 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-undefmodule.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, }, },}复制代码
实现如下样式:
image.png
代码:
<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 } }复制代码
2023-03-02-11-00-59.gif
思路:
1 获取动画目的地节点,获取七个元素的节点放到一个数组里。
2 点击某一天时,获取当前点击的元素节点信息,初始化动画开始位置,然后设置到达目标位置的动画
具体是怎么实现的呢?
获取动画结束位置的 dom 节点信息。
const destinationDom = useRef<HTMLDivElement>(null)复制代码
<div ref={destinationDom} className='destination'></div>复制代码
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>复制代码
我在写的时候在想,可不可以使用 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>复制代码
不同的模块文件有不同的作用域,且只会被执行一次。比如下面的日志只会被打印一次。之后无论在其它文件导入 useRef 多少次,日志都不会再打印了。
let valueconsole.log(1)export function useRef(_value) { if(value) { return value } value = _value return value}复制代码
使用 Ant Design Mobile。但说实话,我只使用到了其中很少的组件,Toast 提示、Popup 弹框,其余很多样式、业务组件都需要自己手写。
直接下载使用 ui 切的图片,没有额外引入第三方 icon 图标库。
虽然装了 mobx 依赖,但是几乎没用到。
因为项目没有那么复杂,就几个页面。用的更多的是 useState。但是如果是为了学习,可以强迫把页面的数据都放到 mobx 里进行管理。
查看文档了解更多 cn.vitejs.dev/guide/env-a…[3]
命令行通过 --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.testVITE_SHAREURL='https://test.share.com.cn'VITE_BASEURL='https://www.test.baidu.com'# .env.preVITE_SHAREURL='https://pre.share.com.cn'VITE_BASEURL='https://www.pre.baidu.com'# .env.prodVITE_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(); })}复制代码
在生产环境下去除 log 日志和 debugger。
defineConfig 可以传入一个函数,通过 config 参数可以获取到模式 mode 的值。
cn.vitejs.dev/config/#con…[4]
export default defineConfig((config) => { return { build: { minify: 'terser', terserOptions: { compress: { // 默认是 false。移除 console drop_console: config.mode === 'prod', // 默认是 true。移除 debugger drop_debugger: config.mode === 'prod', }, } } }})复制代码
image.png
gzip 压缩可以大大节省服务器的网络带宽。
啥是 gzip ?我理解的就是一个能压缩内容的工具。浏览器请求 url时,如果在 request header 中设置属性 accept-encoding: 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 压缩成功。
image.png
使用的是公司封装好的请求工具,不清楚内部是怎么实现的。
虽然我不用管。但我看了下,大概是引入了一个加密的 js 文件工具方法,有几种加密方式。然后接口请求时会判断使用哪种加密方式,对进行携带的参数进行加密。
这其实是 TypeScript 版本问题。
使用 Vscode 写 TS 时,出现一些奇怪的飘红。可能是 TypeScript 版本的问题。选择使用工作目录下的 TS 版本,而不使用 Vscode 的 TS 版本。
09b7ca218e6913063d049d05d54a48e.png
快捷键 ctrl + shift + p
image.png
然后选择使用 Workspace Version
注意:有可能设置版本后,在 vscode 里还是飘 ts 的红。可以尝试把依赖和 package-lock.json 文件都删了,重新下载依赖。
出现这个问题是因为 Vscode 的 Lanuage mode 出现了问题,重新选择,然后重启就可以了。
image.png
项目在低版本的安卓浏览器上会报这个错,在 index.html 里添加如下脚本即可解决。
<script> this.globalThis || (this.globalThis = this); </script>复制代码
前提:在用 vite 创建项目的时候,不要选用 SWC。SWC 不支持下面的配置。vue 项目可以参考 www.cnblogs.com/ygunoil/p/1…[5]
vite.config.ts 里可以配置 babel 插件。
plugins: [react({ babel: { plugins: [ "@babel/plugin-proposal-optional-chaining", "@babel/plugin-proposal-nullish-coalescing-operator" ] } })],复制代码
我们直接在根目录下的 .babelrc 里进行配置。使用 babel 的预设 preset-env,免得一个个配插件。
plugins: [ react({ babel: { // 表示使用根目录下 .babelrc 文件的 babel 配置 babelrc: true, }, })],复制代码
.babelrc
{ "presets": [ [ "@babel/preset-env", { "modules": false } ] ], "targets": "chrome 70"}复制代码
配置好后 vite 就可以使用可选链和空值合并运算符等 es6 新语法了。
在 ts + vite 项目里提示 “path” 找不到。
原因分析:path 模块是 node.js 内置的功能,但是 node.js 本身并不支持 typescript,所以直接在 typescript 项目里使用是不行的。
解决办法
npm install @types/node --save-dev复制代码
image.png
这样我们就方便配置 alias 了。因为它的路径必须是绝对路径。
export default defineConfig({ resolve: { alias: { // 只能是绝对路径 // '@': fileURLToPath(new URL('./src', import.meta.url)), '@': path.resolve('./src') }, },})复制代码
vite 开发和打包的构建逻辑是不一样的。默认打包的目标是支持原生 ESM script 标签[6]、支持原生 ESM 动态导入[7] 和 `import.meta`[8] 的浏览器:
为了让打包后的代码支持传统浏览器,可以使用 legacy 插件。否则如果你的代码使用了可选链、控制合并运算符等 es6 新语法新特性,打包运行低版本浏览器是会报错的。
pnpm add -D @vitejs/plugin-legacy复制代码
vite.config.ts
plugins: [ legacy({ targets: ['> 0.25%', 'last 2 versions and not dead'], }),],复制代码
推荐把 vscode 的终端改为 git bash,解锁更多操作。image.png
之所以学点脚本命令,是因为手动复制粘贴文件到另一个地方太麻烦。感觉用命令行的方式更简单快速。
比如说我想要把打包后的 dist 目录移动到桌面,可以执行:
image.png
这里 desktop_path 是我设置的一个电脑全局环境变量,就是我的电脑桌面路径。
mv dist ${desktop_path}/复制代码
这里列一下我认为比较有用的脚本命令:
# 在当前目录下,把 文件 a 及以下文件移动到 b 目录下(改名也可以用这个命令)mv a b# 创建一个文件夹mkdir test# 创建一个文件touch hello.md# 查看当前目录下所有东西ls# 删除文件或文件夹rm 文件名# 删除文件及以下的文件rm -rf xxx# 查看 shell 全局变量env# 输出全局变量echo $HOME or $HOME# 查看当前文件目录pwd# 清空命令行窗口clear# printenv 查看某个环境变量,变量前不用加 $ 符号了printenv HOME # echo 查看某个环境变量,变量前要加 $ 符号echo $HOME 或者 echo ${HOME}复制代码
在 git bash 中,不管是设置局部变量还是全局变量,退出 shell 就会失效,所以直接在电脑上手动添加环境变量
# 下载npm i http-server -g# 起服务http-server复制代码
有时候我们想要知道,自己写的页面在手机浏览器上被打开是什么样子。
可以将电脑和手机连在同一 wifi 下(有些时候咖啡厅里的 wifi 不可以,可能是防火墙的问题),电脑上启动开发服务器后,在手机浏览器上输入 ip,就可以预览啦。
image.png
你还可以用 usb 数据线连接电脑,在电脑上调试手机上的页面。方便看到打印日志、页面元素、请求发送等。
但有时候感觉用数据线太麻烦了,能不能用电脑无线调试手机上的网页呢?是可以的!
需要使用到 adb(Android Development Bridage)。
9b4b619b4b65b1218ce7251082496e8.jpg
在电脑命令行输入上图,也就是配对码弹出弹框里的 ip 地址和端口号。然后按提示输入配对码(736001)。
adb pair ip:port复制代码
配对成功后,输入命令(注意这里的 ip:port 和上一步的不一样)
adb connect ip:port复制代码
image.png
查看链接的设备
adb devices -l 查看链接的设备复制代码
chrome://inspect/#devices
edge://inspect/#devices
手机打开 edge 或者谷歌或者 app 内嵌的网页,等电脑搜索到。
image.png
1、首先下载 adb。2、手机电脑连在同一 wifi 下3、手机启用开发者模式,打开无线调试,点击使用配对码配对设备4、adb pair ip:port。(手机上显示的 ip:port)5、adb devices -l 查看链接的设备6、adb connect ip:port复制代码
使用下来,限制还是比较多。
首先使用 edge 只能调试在手机上用 edge 浏览器打开的网页。谷歌能调试手机上用 edge 或谷歌打开的网页。
使用谷歌的话调试的话,如果手机浏览器版本太低又会出现点击 inspect 按钮后出现 HTTP/1.1 404 Not Found 的问题。要想调试,只能降低电脑端的浏览器版本了!
如果这些都不受限制,那就更好了!
www.cnblogs.com/wenxuehai/p…[10]
github.com/long-life23…[11]
原网址: 访问
创建于: 2023-04-06 11:05:41
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论