五年使用vue2、vue3经验,我直接上手react

五年使用vue2、vue3经验,我直接上手react

在前端这个行业不管vuereact都是要会的,它们都是很优秀的,在前端的影响力都很大,市面上基本都是在使用这两种框架。

说下现在框架的特性: 数据驱动更新生命周期组件化路由监听数据JSX组件传值(子传父、父传子、全局值交互)配合编译器webpack、vite、rollup等等。

没说错吧,不管是vuereact,又或者微信小程序都是以上几种特性组合而成的。本文会结合vue做对比的方式提出两者的差异点,如果你刚好会vue,正在学习react那么本篇文章应该会对你受益很大。如果你是资深使用react的,可以指出我总结下错误的地方。

本篇所有的代码链接: 代码仓库

本篇所有的DEMO在线预览链接:在线预览

DEMO截图:

路由篇

路由来说,咱们需要知道以下这些就够用了:

  1. 怎么定义路由
  2. 指定路由的界面
  3. 如何切换路由
  4. 如何获取路由的参数,以及获取参数

定义路由

如果你是使用官方推荐的React Router (v7)生成的脚手架npx create-react-router@latest,那么使用上就可以像vue的配置式一样了如下:

ts 复制代码
// routes.ts
import { type RouteConfig, index, route, layout, prefix } from "@react-router/dev/routes";

export default [
    layout('./components/Layout.tsx', [
       index("routes/home.tsx"),
       route('user', './views/user.tsx'),
       route('user/:name', './views/user/detail.tsx'),
    ]),

    ...prefix("concerts", [
        index("./views/concerts/user.tsx"),
      ]),
] satisfies RouteConfig;
  • index就是代表根路径,直接是个文件地址就行
  • route代表子路由,第一个参数是路由地址,第二个参数是路由组件地址
  • layout代表布局,第一个参数是布局的组件地址,第二个参数是各个路由
tsx 复制代码
// Layout.tsx
import { Outlet } from "react-router";

const AppLayout = () => {
  return (
    <div className="flex">
      <div className="w-1/5">
        <App />
      </div>
      <div className="w-4/5">
        <Outlet />
      </div>
    </div>
  );
};

使用Outlet代表路由放的位置。有点类似于Vue<router-view />的样子

  • prefix很明显了就是前缀的意思,类似于vue的baseUrl

以上定义的直接通过运行的地址访问到了

bash 复制代码
http://localhost:5173/
http://localhost:5173/user
http://localhost:5173/user/3367
http://localhost:5173/concerts

使用从零构建一个react项目的npm create vite@latest my-app -- --template react可以通过安装react-router的方式去使用如下:

安装react-router

csharp 复制代码
yarn add react-router

在你从零构建一个react项目中main.jsx中如下:

jsx 复制代码
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'

import App from './App.jsx'
import About from './views/About.jsx'
import AppLayout from './components/layout/index.jsx'
import Page1 from './views/Page1.jsx'
import Page2 from './views/Page2.jsx'
import ConcertsIndex from './views/concerts/Index.jsx'
import City from './views/concerts/city.jsx'
import Trending from './views/concerts/trending.jsx'


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

createRoot(document.getElementById('root')).render(
  <StrictMode>
  <BrowserRouter>
    <Routes>
      <Route path="/" element={<App />} />
      <Route path="about" element={<About />} />

      {/* layout */}
      <Route element={<AppLayout />}>
        <Route path="page1" element={<Page1 />} />
        <Route path="page2" element={<Page2 />} />
      </Route>

      {/* prefix */}
      <Route path="concerts">
        <Route index element={<ConcertsIndex />} />
        <Route path=":city" element={<City />} />
        <Route path="trending" element={<Trending />} />
      </Route>

    </Routes>
  </BrowserRouter>
  </StrictMode>
)

使用<BrowserRouter /> 、<Routes />、<Route /> 来配合使用就行了基本跟上面一样,定义路由名称path,指定组件element

可以使用react-router中的<BrowserRouter /> 、<HashRouter />来切换路由的hash、history的模式

BrowserRouterhistory 模式, 即: http://localhost:5173/home

jsx 复制代码
// BrowserRouter 是 history 模式    
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { BrowserRouter, Routes, Route } from "react-router";

createRoot(document.getElementById('root')).render(
  <StrictMode>
  <BrowserRouter>
    <Routes>
      <Route path="home" element={<App />} />
    </Routes>
  </BrowserRouter>
  </StrictMode>
)

HashRouterhash 模式, 即: http://localhost:5173/#/home

jsx 复制代码
// BrowserRouter 是 hash 模式    
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
import { HashRouter, Routes, Route } from "react-router";

createRoot(document.getElementById('root')).render(
  <StrictMode>
  <HashRouter>
    <Routes>
      <Route path="home" element={<App />} />
    </Routes>
  </HashRouter>
  </StrictMode>
)

这里在提下路由懒加载吧,react中提供了lazySuspense组件来支持路由懒加载,使用起来非常方便。如下包裹:

jsx 复制代码
import { StrictMode, lazy, Suspense } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'

import App from './App.jsx'
import About from './views/About.jsx'
import AppLayout from './components/layout/index.jsx'
import Page1 from './views/Page1.jsx'
import Page2 from './views/Page2.jsx'
import ConcertsIndex from './views/concerts/Index.jsx'
import City from './views/concerts/city.jsx'
import Trending from './views/concerts/trending.jsx'


const LazyAbout = lazy(() => import('./views/About.jsx'))


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

createRoot(document.getElementById('root')).render(
  <StrictMode>
  <BrowserRouter>
    <Suspense fallback={<div>Loading...</div>}>
        <Routes>
        <Route path="/" element={<App />} />
        <Route path="about" element={<About />} />

        {/* layout 布局*/}
        <Route element={<AppLayout />}>
            <Route path="page1" element={<Page1 />} />
            <Route path="page2" element={<Page2 />} />
        </Route>

        {/* prefix 路由前缀 */}
        <Route path="concerts">
            <Route index element={<ConcertsIndex />} />
            <Route path=":city" element={<City />} />
            <Route path="trending" element={<Trending />} />
        </Route>

        {/* lazy 路由懒加载*/}
        <Route path="lazy-about" element={<LazyAbout />} />
        </Routes>
    </Suspense>
  </BrowserRouter>
  </StrictMode>
)

切换路由

react-router中,切换路由可以使用useNavigate

jsx 复制代码
// nav/index.jsx

import React from "react";
import { useNavigate } from 'react-router';
import { Button, Flex } from 'antd';

 const NavIndex = () => {
    const navigate = useNavigate();

    const onClick1 = () => {
        navigate('/nav/page1');
    }

    const onClick2 = () => {
        navigate('/nav/page2');
    }

    const onClick3 = () => {
        navigate('/nav/123');
    }

  return (
    <>
     <Flex gap="small" wrap>
        <Button type="primary" onClick={onClick1}>去到 /nav/page1页面</Button>
        <Button type="primary" onClick={onClick2}>去到 /nav/page2页面</Button>
         <Button type="primary" onClick={onClick3}>去到 /nav/:id 页面</Button>
    </Flex>
    </>
  );
};

export default NavIndex;

main.jsx中增加

jsx 复制代码
import { StrictMode, lazy, Suspense } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'

import App from './App.jsx'
import About from './views/About.jsx'
import AppLayout from './components/layout/index.jsx'
import Page1 from './views/Page1.jsx'
import Page2 from './views/Page2.jsx'
import ConcertsIndex from './views/concerts/Index.jsx'
import City from './views/concerts/city.jsx'
import Trending from './views/concerts/trending.jsx'

import NavIndex from './views/nav/Index.jsx'
import NavPage1 from './views/nav/Page1.jsx'
import NavPage2 from './views/nav/Page2.jsx'
import NavPage3 from './views/nav/Page3.jsx'


const LazyAbout = lazy(() => import('./views/About.jsx'))


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

createRoot(document.getElementById('root')).render(
  <StrictMode>
  <BrowserRouter>
  <Suspense fallback={<div>Loading...</div>}>
    <Routes>
      <Route path="/" element={<App />} />
      <Route path="about" element={<About />} />

      {/* layout */}
      <Route element={<AppLayout />}>
        <Route path="page1" element={<Page1 />} />
        <Route path="page2" element={<Page2 />} />
      </Route>

      {/* prefix */}
      <Route path="concerts">
        <Route index element={<ConcertsIndex />} />
        <Route path=":city" element={<City />} />
        <Route path="trending" element={<Trending />} />
      </Route>

      <Route path="lazy-about" element={<LazyAbout />} />

      {/* 路由切换相关代码 */}
      <Route path="nav">
        <Route index element={<NavIndex />} />
        <Route path='page1' element={<NavPage1 />} />
        <Route path='page2' element={<NavPage2 />} />
        <Route path=':id' element={<NavPage3 />} />
      </Route>

    </Routes>
   </Suspense>
  </BrowserRouter>
  </StrictMode>
)

navigate('/nav/page1');做为路由跳转,navigate(-1);可以做为路由返回。

路由参数传参获取

路由传参数有以下几种方式:

  • 直接拼接到路由上 navigate('/nav/page1?id=1&name=zhangsan');
  • 使用 navigate state参数传递更复杂的数据(例如对象或数组)。这些数据不会出现在 URL 中,但可以在目标页面中访问,有点像是全局存储了一份数据。
  • 路由params传参数 使用useParams接收

第一种:navigate('/nav/page1?id=1&name=zhangsan');方式使用react-router中的useSearchParams获取 如下:

jsx 复制代码
// nav/page1.jsx
import React from "react";
import { useNavigate, useSearchParams } from 'react-router';
import { Button } from 'antd';
 const Page1 = () => {
  const navigate = useNavigate();
  const [searchParams] = useSearchParams();

  const onBack = () => {
    navigate(-1)
  }
  console.log(searchParams.get('id'), searchParams.get('name'), 'id,name'); // 输出: 1 zhangsan id,name

  return (
    <>
    <div>
      <h1>Nav Page1</h1>

      <Button type="primary" onClick={onBack}>返回</Button>
    </div>
    </>
  );
};

export default Page1;

第二种:navigate state参数传递更复杂的数据如下,这种方式是利用history、hashAPI做数据存储的都是存在你浏览器记录里面了,所以不能把链接分享给别人去使用

jsx 复制代码
  const onClick2 = () => {
        navigate('/nav/page2', {
            state: {
                id: 2,
                name: 'lisi'
            }
        });
    }

获取方式如下:

jsx 复制代码
// nav/page2.jsx
import React from "react";
import { useNavigate, useLocation } from 'react-router';
import { Button } from 'antd';
 const Page2 = () => {
  const navigate = useNavigate();

  const location = useLocation();
  const state = location.state || {};

  console.log(state, 'state'); // 输出:{ "id": 2, "name": "lisi" }  'state'

  const onBack = () => {
    navigate(-1)
  }
  return (
    <>
    <div>
      <h1>Nav Page2</h1>
      <Button type="primary" onClick={onBack}>返回</Button>
    </div>
    </>
  );
};

export default Page2;

第三种:路由params传参数

jsx 复制代码
 const onClick3 = () => {
        navigate('/nav/123');
    }
jsx 复制代码
import React from "react";
import { useNavigate, useParams } from 'react-router';
import { Button } from 'antd';
 const Page3 = () => {
  const navigate = useNavigate(); 
  const {id} = useParams();
  console.log(id, 'id'); // 输出:123 id

  const onBack = () => {
    navigate(-1)
  }
  return (
    <>
    <div>
      <h1>Nav Page3</h1>
      <Button type="primary" onClick={onBack}>返回</Button>
    </div>
    </>
  );
};

export default Page3;

路由篇总结

其实看到这里也算是完全掌握路由相关的使用了,咱们接着往下看

组件编写以及JSX使用

组件的模块化,一处编写,多处使用。咱们接下来先编写一个简单的react 组件

jsx 复制代码
// start/Index.jsx
import React from "react";

export default function Start() {
  return (
    <div>
      <h1>About1</h1>
      <h1>About2</h1>
     </div>
  );
}

Fragment组件使用

不想要父元素(去掉一层元素)div包裹可以使用Fragment组件,如下:

jsx 复制代码
import React, { Fragment } from "react";

export default function Start() {
  return (
    <Fragment>
      <h1>About</h1>
      <h1>About2</h1>
    </Fragment>
  );
}

Fragment可以简写如下:

jsx 复制代码
import React from "react";

export default function Start() {
  return (
    <>
      <h1>About</h1>
      <h1>About2</h1>
    </>
  );
}

JSX 基础使用方式

jsx 复制代码
import React from "react";

export default function Start() {
    const data = [
        {
            name: 1,
            id: 1
        },
        {
            name: 2,
            id: 2
        },
       ]

       const currentType = true;



       const onBtnClick = () => {
        console.log('我是按钮点击了click')
       }

  return (
    <>
      <h1>About</h1>
      <h1>About2</h1>
      {/* 循环渲染数据 */}
      <ul>
        {
          data.map((item) => (<li key={item.id}>{item.name}</li>))
        }
      </ul>
      <ul>
        {
          data.filter((item) => item.name !== 1).map((item) => <li key={item.id}>{item.name}</li>)
        }
      </ul>

        {/* 三目运算 */}
        <div>
        { currentType ? <h1>true 111111</h1> : <h1>false22222</h1>}
      </div>

      {/* 添加class类名 */}

      <div></div>

      {/* 行内样式 */}
      <div style={{color: 'red', fontSize: '16px'}}>我是什么颜色</div>

      {/* 事件绑定 click*/}
      <button onClick={onBtnClick}>我是按钮</button>
    </>
  );
}

父子组件传参数

其实在react中省去了子传父 Vue 中 emit事件了,直接利用props传一个函数方法作为回调去做的,如下:

jsx 复制代码
// parent.jsx

import React, { useState } from 'react';

import Children from './children';

const Parent = () => {
  const [name, setName] = useState('张三');

  return (
    <div>
      <h1>About Page</h1>
      <Children name={name} onClick={setName}/>
    </div>
  );
};


export default Parent;



// children.jsx
import React from 'react';

const Children = ({ name, onClick}) => {
  return (
    <div>
      <h1>{ name }</h1>
      <button onClick={() => {onClick('李四')}}> 点击触发【子传父】</button>
    </div>
  );
};


export default Children;

以上就是一个简单的组件传值交互,就是把name给子组件去使用,并且把函数setName传了下去,做为点击事件的回调函数。

第二种方案是向下透传,类似于vuePrvider、inject,使用useContext、createContext

第一步创建index.js

js 复制代码
import { createContext } from 'react'
// 创建上下文
export const DataContext = createContext(null)

第二步使用这个上下文方法

jsx 复制代码
// index.jsx 
import React, { useState } from "react";
import { DataContext } from './index'

import Children from "./children";

 const ContextPage = () => {
 const [name, setName] = useState('张三');


  return (
    // 注意这里是使用的 DataContext.Provider 并且必须是value字段,value 可以是字符串,对象,数组等
    <DataContext.Provider value={{ name, setName }}> 
       <Children />
    </DataContext.Provider>
  );
};


export default ContextPage;

//children.jsx 
import React, { useContext } from 'react';
import { DataContext } from './index'

const Children = () => {
    // useContext 方法入参数就是前面定义好的上文DataContext,context接收后可以直接使用就行
  const context = useContext(DataContext);

  return (
    <div>
      <h1>{ context.name }</h1>
      <button onClick={() => {context.setName('李四')}}> 点击触发【子传父】</button>
    </div>
  );
};


export default Children;

useContext、DataContext是透传数据,不止父子组件,只要是父级<DataContext.Provider>包裹内以下任何一级使用,上面咱们传输了一个对象name, setName然后就可以给子级组件去使用了,注意绑定在<DataContext.Provider>上的一定是value

生命周期

在使用react hooks中省去生命周期,可以直接用useState、useEffect去代替。这里先不去着重讲这几个hooks的用法,请继续往下看

  • useState 代表初始化状态 类似于VueonCreated,其实在VUE组合式API中生命周期也基本可以省去了。
  • useEffect 代表Vue中的onMounted、onDestroyed事件

简单实用如下:

jsx 复制代码
export const Demo = () => {
  console.log('组件初始化');
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('组件挂载了');
    // 比如使用了window.addEventListener 
    return () => {
      console.log('组件卸载了');
       // 比如使用了window.addEventListener 这里可以使用window.removeEventListener清楚掉
    }
  })
}

插槽

react对组件位置的存放更简单,不用定义固定的位置(<slot />)或者说具名插槽(<slot name="footer" />),可以直接使用如下:

先创建layout.jsx文件:

jsx 复制代码
import React from "react";

export default function Layout({ children, headerComponent }) {
  return (
    <div>
      <header>
       { headerComponent }
      </header>
      <main>{children}</main>
    </div>
  );
}

再创建一个header.jsx文件以及main.jsx文件如下:

jsx 复制代码
// header.jsx`

import React from "react";


const Header = () => {
    return (
        <div>我是头部Header部分代码</div>
    )
}

export default Header

//main.jsx

import React from "react";

const Main = () => {
    return (
        <div>我是主体Main部分</div>
    )
}

export default Main

接下来直接创建index.jsx文件展示用法

jsx 复制代码
import React from "react";

import Main from "./main";
import Header from "./header";
import Layout from "./layout";

const SoltPage = () => {
  return (
    <Layout headerComponent={<Header />}>
        <Main>
        </Main>
    </Layout>
  );
};

export default SoltPage

Layout组件中的参数children部分就是插槽的默认部分,向Layout直接传了一个<Header/>,然后在Layout组件就直接用了{headerComponent},也挺省事的。

react 样式编写

react中样式使用css、less、scss跟在vue中一样,直接下载依赖,然后直接创建对应的.less、.scss、.css文件以import引入到对应的组件即可,比如import './index.css'; import './index.less'; import './index.scss'

但是以上没有scope组件样式隔离的效果,假如类名一样等情况会相互影响的。

react经常使用的样式隔离方案就是使用styled-components插件,这里咱们简单过下如何使用 ,如果对原理有兴趣可以参考我之前《为什么在vue中style-components没有火起来?》文章

下载插件:yarn add styled-components, 如果是vscode编辑器可以下载插件vscode-styled-components,然后就能像写css样有代码提示了 使用如下:

jsx 复制代码
import React from "react";
import styled from "styled-components";

// 写完就是一个组件,可以直接使用
const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: #f0f0f0;
  min-height: calc(100vh - 40px);
`;

const StylePage = () => {
 return (
    <Container>
      <div>
        <h1>StylePage</h1>
      </div>
    </Container>
 )
}

export default StylePage;

还可以直接更改子元素的样式如下:

jsx 复制代码
import React from "react";
import styled from "styled-components";


const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: #f0f0f0;
  min-height: calc(100vh - 40px);

  h1 {
    color: red;
  }
`;

const StylePage = () => {
 return (
    <Container>
      <div>
        <h1>StylePage</h1>
      </div>
    </Container>
 )
}

export default StylePage;

还可以进行props传参数使用如下:

jsx 复制代码
import React from "react";
import styled from "styled-components";


const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: #f0f0f0;
  min-height: calc(100vh - 40px);

  h1 {
    color: red;
  }
`;


const Button = styled.button`
  background-color: #007bff;
  color: ${(props) => props.color};
  border: none;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
`
const StylePage = () => {
 return (
    <Container>
      <div>
        <h1>StylePage</h1>
        <Button color="white">Click Me</Button>
        <Button color="red">Click Me</Button>
        <Button color="blue">Click Me</Button>
      </div>
    </Container>
 )
}
export default StylePage;

还可以跟组件一样嵌套使用如下:

jsx 复制代码
import React from "react";
import styled from "styled-components";


const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background-color: #f0f0f0;
  min-height: calc(100vh - 40px);

  h1 {
    color: red;
  }
`;


const Button = styled.button`
  background-color: #007bff;
  color: ${(props) => props.color};
  border: none;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
  font-size: 16px;
`

const HoverButton = styled(Button)`
  transition: background-color 0.3s ease;

  &:hover {
    background-color: #0056b3;
  }
`
const StylePage = () => {
 return (
    <Container>
      <div>
        <h1>StylePage</h1>
        <Button color="white">Click Me</Button>
        <Button color="red">Click Me</Button>
        <Button color="blue">Click Me</Button>

        <HoverButton color="white">Click Me</HoverButton>
        <HoverButton color="red">Click Me</HoverButton>
        <HoverButton color="blue">Click Me</HoverButton>
      </div>
    </Container>
 )
}

export default StylePage;

以上就是styled-components插件的使用,还有更改用法更多请看官方文档, 还有使用tailwindcss的方式,大致就是定义好了类名,直接去使用就行(个人不是很喜欢这种)。

Hooks 核心应用

以下内置的几个hooks重重之重 每个都要必须熟练 掌握 useStateuseEffectuseContextuseReduceruseCallbackuseMemouseRef,我接下来会针对每个的使用场景 以及注意事项用法都仔细的讲一遍

useState

useState是用于在函数组件中管理组件的状态,它返回一个状态值和一个更新状态的函数。使用如下:

以下就是一个简单的例子,点击按钮+1

jsx 复制代码
import React from "react";
import { Button, Divider } from "antd";

const UseStatePage = () => {
 const [count, setCount] = React.useState(0);
  return (
    <>
    <div>
      <Button onClick={() => setCount(count + 1)}>点击+1</Button>
      <h1>{count}</h1>
    </div>
    <Divider />
    </>
  );
};

export default UseStatePage;

再来看一个案例点击按钮如果触发两次setCount2(count2 + 1)会怎么样?

jsx 复制代码
import React from "react";
import { Button, Divider } from "antd";

const UseStatePage = () => {
 const [count, setCount] = React.useState(0);
 const [count2, setCount2] = React.useState(0);
  return (
    <>
    <div>
      <Button onClick={() => setCount(count + 1)}>点击+1</Button>
      <h1>{count}</h1>
    </div>
    <Divider />

    <div>
      <h1>陷阱:错误❌的使用示例</h1>
      <Button 
        onClick={() => {
          setCount2(count2 + 1)
          setCount2(count2 + 1)
        }}
      >
        按钮2: 点击+1+1
        </Button>
      <h1>{count2}</h1>
    </div>
    </>
  );
};

export default UseStatePage;

以上每次点击按钮2: 点击+1+1还是每次+1 ,不会按照预期+2,因为react的执行时机是异步的。请再接着往下看

jsx 复制代码
// 加一个定时器
 const [count3, setCount3] = React.useState(0);
 <div>
      <h1>陷阱:错误❌的使用示例</h1>
      <Button 
        onClick={() => {
          setCount3(count3 + 1)
          
          setTimeout(() => {
            setCount3(count3 + 1)
          }, 500)
        }}
      >
        点击+1+1
        </Button>
      <h1>{count3}</h1>
    </div>

加上定时器也是一样的不会每次加2,官方的解释是:这是因为 状态表现为就像一个快照。更新状态会使用新的状态值请求另一个渲染,但并不影响在你已经运行的事件处理函数中的 count JavaScript 变量。那么如何解决这个问题呢?

传入一个函数即可:(请注意a 更改是函数的入参数,不是原来的count4)

jsx 复制代码
const [count4, setCount4] = React.useState(0);
<div>
      <h1>正确✅使用示例</h1>
      <Button 
        onClick={() => {
          setCount4((a) => a + 1)
          setCount4((a) => a + 1)
        }}
      >
        点击+1+1
        </Button>
      <h1>{count4}</h1>
    </div>

Object类型要如何更改数据呢?请看以下示例

jsx 复制代码
import React, { useState } from "react";
import { Button } from "antd";

const UseStateOther = () => {
    const [count, setCount] = useState({
      name: 'zhangsan',
      age: 18
    });

    return (
        <>
          <h1> Object 类型使用示例</h1>
          <Button onClick={() => {
            setCount({
              ...count,
              age: count.age + 1
            })
          }}>
            age + 1
          </Button>
          <pre>{JSON.stringify(count,null,2)}</pre>
        </>
    )
}

export default UseStateOther;

setCount({...count, age: count.age + 1 }),就是把原来的参数使用...,然后再去更改age的值,那么有没有更方便的更改方式呢?能直接更改就生效了呢?

如下咱们简单写个useImmer方法用来便捷的更改Object类型,使之能直接使用count.age++来更新

jsx 复制代码
const useImmer = (initState) => {
  const [state, setState] = useState(initState);

  const setStateImmer = (callback) => {
    const data = typeof state === 'object' ?  { ...state } : state
    if(typeof callback === 'function') {
      callback(data)
    }
    setState(data)
  }
 
  return [state, setStateImmer];
};

大致就是对useStatesetData包裹了一层方法用来处理Object等类型的更新,使用如下:

jsx 复制代码
 const [data2, setData2] = useImmer({
    name: 'zhangsan',
    age: 18
  });

   <div>
     <h1> Object 简单使用示例</h1>
     <Button onClick={() => {
        setData2((draft) => {
          draft.age++
        })
      }}>
        age + 1
      </Button>
      <pre>{JSON.stringify(data2,null,2)}</pre>
    </div>
    <Divider />

上面只是简单的useImmer的函数封装,应该还有好多没有考虑到的情况,大家可以直接使用官方推荐的immerjs/use-immer 依赖,直接安装去使用就行了。

接下来咱们对数组的数据处理方式也做个简单介绍,对数组的增加、删除、改,我下面就直接使用use-immer依赖宝使用了,如下:

jsx 复制代码
import React from "react";
import { Button, Divider } from "antd";
import { useImmer } from 'use-immer'


const UseStateArray = () => {
    const [data, setData] = useImmer([
        {
          name: 'zhangsan',
          age: 18
        }
    ]);

    return (
        <>
        <div>
          <h1>数组 增加</h1>
          <Button onClick={() => {
            setData((draft) => {
              draft.push({
                name: 'lisi',
                age: 18
              })
            })
          }}>
           加一条数据
          </Button>
          <pre>{JSON.stringify(data,null,2)}</pre>
        </div>

          <Divider />


          <div>
          <h1>数组删除最后一条数据</h1>
          <Button onClick={() => {
            setData((draft) => {
              draft.splice(-1, 1)
            })
          }}>
           减一条数据
          </Button>
          <pre>{JSON.stringify(data,null,2)}</pre>
        </div>

        <Divider />


        <div>
          <h1>数组对第一条数据age + 1</h1>
          <Button onClick={() => {
            setData((draft) => {
                draft[0].age++
            })
          }}>
           age++
          </Button>
          <pre>{JSON.stringify(data,null,2)}</pre>
        </div>
        </>
    )
}


export default UseStateArray;

useState 总结: 如果更改基本类型可以直接使用useState,如果需要更改数组、对象等更便捷可以使用useImmer方法。

useEffect

注意再开发模式中由于react的严格机制,useEffect会被更新2次 ,主要是怕你使用不当导致出现的bug

  • useEffect 第二个字段为空数组时,可以当作onMounted去使用,只在首次触发,不做任何监听。
  • useEffect 第二个字段不为空数组时,可以当作onUpdated去使用,监听到该数组内数据变化,会触发。
  • useEffect 第二个字段不传时 ,只要该组件被reload就会被触发(该组件任意值更新)。
  • useEffect 第一个参数return一个函数,可以当作onUnmounted去使用。

useEffect可以把它当作onMounted、onUnmounted、onUpdated、去使用。使用如下:

jsx 复制代码
// indx.jsx
import React, { useState, useEffect } from "react";
import { Button, Divider } from 'antd'
import Children from "./children";

const UseEffectPage = () => {
    const [count, setCount] = useState(0);
    const [data, setData] = useState(0);

    useEffect(() => {
      setData((a) => a + 1)
    }, [])

    return (
        <>
          <h1>基本使用</h1>
          <Button onClick={() => setCount(count + 1)}>点击更改值</Button>
          <h3>{count}</h3>

          <h2>useEffect的触发次数: {data}</h2>
          <Children count={count} />
          <Divider />
        </>
    )
};

export default UseEffectPage;

// children.jsx
import React, { useEffect } from "react";

const Children = ({count}) => {
  const [data, setData] = React.useState(0);
  
  useEffect(() => {
    setData((a) => a + 1)
  }, []);

  return (
    <div>
      <h1>{count}</h1>
      <h2>useEffect子组件更新的次数{data}</h2>
    </div>
  );
};

export default Children;

在点击按钮时,由于useEffect为空,不做任何监听,不会产生触发。

所以在useEffect第二个参数为空 时,就可以单纯当作onMounted去使用。

useEffect如果监听count,在点击按钮时就可以看到更新了(每次加1),如下:

jsx 复制代码
useEffect(() => {
  setData((a) => a + 1)
}, [count])

如果第二个参数直接不填会怎么样?如下:

jsx 复制代码
// children.jsx
import React, { useEffect } from "react";

const Children = ({count}) => {
  const [data, setData] = React.useState(0);
  
  useEffect(() => {
    // setData((a) => a + 1)
    console.log('子组件更新了')
  });

  return (
    <div>
      <h1>{count}</h1>
      <h2>useEffect子组件更新的次数{data}</h2>
    </div>
  );
};

export default Children;

其实会看到每次父组件更改count值后,子组件进行reload,由于useEffect第二个参数没传,导致跟子组件一样,每次触发2次。

  • useEffect 第一个参数return一个函数,可以当作onUnmounted去使用,示例如下
jsx 复制代码
// children.jsx
import React, { useEffect } from "react";

const Children = ({count}) => {
  const [data, setData] = React.useState(0);
  
  useEffect(() => {
    setData((a) => a + 1)
    
    return () => {
      console.log("useEffect子组件卸载");
    };
  }, []);

  return (
    <div>
      <h1>{count}</h1>
      <h2>useEffect子组件更新的次数{data}</h2>
    </div>
  );
};

export default Children;

请再看下面一个示例:

jsx 复制代码
// index.jsx
import React, { useState, useEffect } from "react";
import { Button, Divider } from 'antd'
import Children from "./children";

const UseEffectPage = () => {
    const [count, setCount] = useState(0);
    const [data, setData] = useState(0);
    console.log('父组件更新了');

    useEffect(() => {
      setData((a) => a + 1)
    }, [count])

    return (
        <>
          <h1>基本使用</h1>
          <Button onClick={() => setCount(count + 1)}>点击更改值</Button>
          <h3>{count}</h3>

          <h2>useEffect的触发次数: {data}</h2>
          <Divider />
          
          <Children />
          <Divider />
        </>
    )
};

export default UseEffectPage;


// children.jsx
import React, { useEffect } from "react";
import { Button, Divider } from 'antd'

const Children = () => {
  const [data, setData] = React.useState(0);
  const [count1, setCount] = React.useState(0);

  console.log('子组件更新了');
  
  useEffect(() => {
    setData((a) => a + 1)

    return () => {
      console.log("useEffect子组件卸载");
    };
  }, []);

  return (
    <div>
      <h2>useEffect子组件更新的次数{data}</h2>
      <Divider />

      <Button onClick={() => setCount(count1 + 1)}>子组件+1</Button>
      <h3>子组件{count1}</h3>
    </div>
  );
};

export default Children;

可以把关注点看在两个console.log('父组件更新了');、console.log('子组件更新了');,在点击了父子 组件的按钮,会怎样执行,在这里讲下react的更新机制

  • 父组件中有字段更新更新,就会触发父组件以及子组件(没用props字段也会)reload
  • 子组件字段有更新,当前子组件会被重reload,父组件不会从新reload

那么有没有办法解决父组件更新,子组件不用reload呢?也是有的哈可以使用mome函数包裹子组件一层。如下:

jsx 复制代码
import React, { useEffect, memo } from "react";
import { Button, Divider } from 'antd'

const Children = memo(() => {
  const [data, setData] = React.useState(0);
  const [count1, setCount] = React.useState(0);

  console.log('子组件更新了');
  
  useEffect(() => {
    setData((a) => a + 1)

    return () => {
      console.log("useEffect子组件卸载");
    };
  }, []);

  return (
    <div>
      <h2>useEffect子组件更新的次数{data}</h2>
      <Divider />

      <Button onClick={() => setCount(count1 + 1)}>子组件+1</Button>
      <h3>子组件{count1}</h3>
    </div>
  );
});

export default Children;

这个时候父组件有更新,子组件就不会更新了。除非是子组件上有参数更新了,子组件才会更新,比如:

jsx 复制代码
// index.jsx
import React, { useState, useEffect } from "react";
import { Button, Divider } from 'antd'
import Children from "./children";

const UseEffectPage = () => {
    const [count, setCount] = useState(0);
    const [count1, setCount1] = useState(0);
    const [data, setData] = useState(0);

    console.log('父组件更新了');

    useEffect(() => {
      setData((a) => a + 1)
    }, [])

    return (
        <>
          <h1>基本使用</h1>
          <Button onClick={() => setCount(count + 1)}>点击更改值count</Button>
          <h3>{count}</h3>

          <Button onClick={() => setCount1(count1 + 1)}>点击更改值count1</Button>
          <h3>{count1}</h3>

          <h2>useEffect的触发次数: {data}</h2>
          <Divider />
          
          <Children count={count} />
          <Divider />
        </>
    )
};

export default UseEffectPage;

// children.jsx
//这里跟上面一样,请注意 我把count绑定在子组件上

在点击点击更改值coun 后发现父子组件都做reload了。 在点击点击更改值count1 后就父组件reload,子组件不会。

**总结:**关于reload的组件更新机制刚才也说了,如何利用mome优化也有使用示例,useEffect的第二个参数不传、为空数组、为有数据相关都有说明示例。

useRef

注意这里跟vueref超级不一样react 中改变 ref 不会触发重新渲染 ,所以 ref 不适合用于存储期望显示在屏幕上的信息。如有需要,使用useState代替。

写个错误示例: 这里的count值会一直为0,因为useRefcurrent值不会触发reload

jsx 复制代码
// index.jsx 
import React, { useRef, useEffect } from "react";
import { Button } from "antd";

const useRefPage = () => {
    const count = useRef(0);
    console.log('组件重新渲染!!!')
     
    useEffect(() => {
        console.log(count.current)
    }, [count])

    return (
        <div>
            <h1>错误❌示例</h1>
            <Button onClick={() => {
                count.current++
            }}>
                Ref点击加加
            </Button>
            <h1>{count.current}</h1>
        </div>
    )
}

export default useRefPage

useRef更适合用来绑定dom,存储定时器, 存储信息数据,比如:

jsx 复制代码
import { useState, useRef } from 'react';

export default function Stopwatch() {
  const [startTime, setStartTime] = useState(null);
  const [now, setNow] = useState(null);
  const intervalRef = useRef(null);

  function handleStart() {
    setStartTime(Date.now());
    setNow(Date.now());

    clearInterval(intervalRef.current);
    intervalRef.current = setInterval(() => {
      setNow(Date.now());
    }, 10);
  }

  function handleStop() {
    clearInterval(intervalRef.current);
  }

  let secondsPassed = 0;
  if (startTime != null && now != null) {
    secondsPassed = (now - startTime) / 1000;
  }

  return (
    <>
      <h1>Time passed: {secondsPassed.toFixed(3)}</h1>
      <button onClick={handleStart}>
        开始
      </button>
      <button onClick={handleStop}>
        停止
      </button>
    </>
  );
}

以上示例用useRef来存储定时器,useRefcurrent值不会触发reload。并且在组件重新渲染(更新state)时,useRefcurrent值不会改变。

在或者用来操作DOM,如下:

jsx 复制代码
import React, { useRef, useEffect } from 'react';

function MyComponent() {
  const inputRef = useRef(null);

  useEffect(() => {
    // 页面加载完成后聚焦到输入框
    inputRef.current.focus();
    console.log(inputRef.current.value); // 获取输入框的值
  }, []);

  return <input ref={inputRef} />;
}

警告⚠️:useRef不能像Vueref绑定在组件上 可以获取子组件的值。可以传输给子组件,交给子组件去绑定dom,如下:

jsx 复制代码
//index.jsx
const componentRef = useRef(null);
<DomRef ref={componentRef}/>

// Dom.jsx

import React, { useRef, useEffect } from 'react';

const DomRef = ({ref}) => {
  const inputRef = useRef(null);

  useEffect(() => {
    // 页面加载完成后聚焦到输入框
    inputRef.current.focus();
    console.log(inputRef.current.value); // 获取输入框的值
  }, []);

  return (
    <>
    <h1>绑定DOM 示例</h1>
     <input ref={inputRef} />
     <a ref={ref}>我是A标签</a>
    </>
  );
}
export default DomRef;

请注意⚠️: 如果你是react v18以及以下版本,需要用forwardRef函数包裹下才能向下传输ref值,如下:(v19该API已废弃可以像上面直接传输ref)

jsx 复制代码
import { forwardRef } from 'react'

const DomRef = forwardRef(({ref}) => {
  const inputRef = useRef(null);

  useEffect(() => {
    // 页面加载完成后聚焦到输入框
    inputRef.current.focus();
    console.log(inputRef.current.value); // 获取输入框的值
  }, []);

  return (
    <>
    <h1>绑定DOM 示例</h1>
     <input ref={inputRef} />
     <a ref={ref}>我是A标签</a>
    </>
  );
})
export default DomRef;

useReducer

参数描述 const [state, dispatch] = useReducer(reducer, initialArg, init?)

  • state 是当前状态值
  • dispatch 是一个函数,用于触发状态更新,它接收一个参数 action,可以是一个对象或一个函数。
  • reducer 是一个函数,它接收两个参数 stateaction,并根据 action 的类型返回一个新的状态值。
  • initialArg 是一个可选参数,用于初始化状态值,如果提供了 init 函数,则 initialArg 将作为 init 函数的参数。

简单使用示例如下:

jsx 复制代码
// index.jsx
import { useReducer } from 'react';

function reducer(state, action) {
  if (action.type === 'incremented_age') {
    return {
      ...state,
      age: state.age + 1
    };
  }
  throw Error('Unknown action.');
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42, name: 'Joe' });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        Increment age
      </button>
      <p>Hello {start.name}! You are {state.age}.</p>
    </>
  );
}

还可以把dispatch传给子组件去触发<Children dispatch={dispatch}/>

猛的一看不就useStatesetSate放在了reducer函数上了,其实也确实这个样,这样会让数据状态管理更加清晰,集中,方便维护。

咱们以useState、useReducer 分别实现一个对数据{ age: 42, name: 'Joe' }的更改,可以做个对比如下:

jsx 复制代码
// reducer.jsx
import { useReducer } from 'react';

function reducer(state, action) {
  if (action.type === 'incremented_age') {
    return {
      ...state,
      age: state.age + 1
    };
  }

  if(action.type === 'decremented_age') {
    return {
      ...state,
     age: state.age -1
    };
  }

  if(action.type === 'change_name') {
    return {
      ...state,
     name: '张三'
    };
  }
  throw Error('Unknown action.');
}

export default function ReducerCounter() {
  const [state, dispatch] = useReducer(reducer, { age: 42, name: 'John' });

  return (
    <>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        增加年龄
      </button>
      <button onClick={() => {
        dispatch({ type: 'decremented_age' })
      }}>
        减少年龄
      </button>
      <button onClick={() => {
        dispatch({ type: 'change_name' })
      }}>
        更改名字
      </button>
      <p>Hello{state.name}! You are {state.age}.</p>
    </>
  );
}

// state.jsx
import { useState } from 'react';


export default function StateCounter() {
  const [data, setData] = useState({ age: 42, name: 'John' });

  const incremented = () => {
    setData({
        ...data,
        age: data.age + 1
    })
  }
  const decremented = () => {
    setData({
        ...data,
        age: data.age - 1
    })
  }
  const change = () => {
    setData({
        ...data,
        name: '张三'
    })
  }

  return (
    <>
      <button onClick={incremented}>
        增加年龄
      </button>
      <button onClick={decremented}>
        减少年龄
      </button>
      <button onClick={change}>
        更改名字
      </button>
      <p>Hello{data.name}! You are {data.age}.</p>
    </>
  );
}

可以看出useReducer其实把数据处理更加聚合,集中,清晰了。

use-immer依赖包中也有useImmerReducer可以直接使用,咱们把上面的useReducer改成useImmerReducer可以看到会更清晰一些,如下:

jsx 复制代码
import React from 'react';
import { useImmerReducer } from 'use-immer'
function reducer(draft, action) {
    switch (action.type) {
        case 'incremented_age':
            draft.age++
            break;
        case 'decremented_age':
            draft.age--
        break;
        case 'change_name':
            draft.name = '张三'
        break;
        default:
            throw Error('Unknown action.');
    }
}

export default function ImmerReducerCounter() {
  const [state, dispatch] = useImmerReducer(reducer, { age: 42, name: 'John' });

  return (
    <>
     <h1>useImmerReducer 管理</h1>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        增加年龄
      </button>
      <button onClick={() => {
        dispatch({ type: 'decremented_age' })
      }}>
        减少年龄
      </button>
      <button onClick={() => {
        dispatch({ type: 'change_name' })
      }}>
        更改名字
      </button>
      <p>Hello{state.name}! You are {state.age}.</p>
    </>
  );
}

注意事项:

    1. reducer方法中第一个值在没有使用useImmerReducer是不可以更改的
    1. 请确定好type类型,避免使用**魔法字符串(incremented_age、decremented_age、change_name)**去判断,可以使用一个枚举类型,或者一个MAP,减少类型错误的发生

useContext

这个其实在父传子 哪里有说有具体用法,这里就不多说了可以直接在上面查看<DataContext.Provider value={{ name, setName }}>

useContext可以搭配useReducer做向下深度交互,咱们以前面的useReducer示例用 useContext来重新写下:

jsx 复制代码
// index.js
import { createContext } from 'react'
export const DataContext = createContext()

// index.jsx
import React from "react";
import { DataContext } from "./index";
import { useImmerReducer } from 'use-immer'

import ViewComponent from './view'
import OperatorButton from "./operator";
function reducer(draft, action) {
    switch (action.type) {
        case 'incremented_age':
            draft.age++
            break;
        case 'decremented_age':
            draft.age--
        break;
        case 'change_name':
            draft.name = '张三'
        break;
        default:
          throw Error('Unknown action.');
    }
}

const UseContextPage = () => {
   const [state, dispatch] = useImmerReducer(reducer, { age: 42, name: 'John' });

  return (
    <DataContext.Provider 
    value={{
      state,
      dispatch
    }}>
      <OperatorButton />
      <ViewComponent />
    </DataContext.Provider>
  );
};

export default UseContextPage;

// view.jsx
import React, { useContext } from "react";
import { DataContext } from './index'

const ViewComponent = () => {
    const { state } = useContext(DataContext);
  return (
    <div>
     <p>Hello{state.name}! You are {state.age}.</p>
    </div>
  );
};

export default ViewComponent;

// operator.jsx
import React, { useContext } from "react";
import { DataContext } from './index'

const OperatorButton = () => {
    const { dispatch } = useContext(DataContext);
    
  return (
    <>
    <h1>useContext + useReducer  管理</h1>
      <button onClick={() => {
        dispatch({ type: 'incremented_age' })
      }}>
        增加年龄
      </button>
      <button onClick={() => {
        dispatch({ type: 'decremented_age' })
      }}>
        减少年龄
      </button>
      <button onClick={() => {
        dispatch({ type: 'change_name' })
      }}>
        更改名字
      </button>
    </>
  )
};

export default OperatorButton;

以上只是一个简单使用useContext的示例,其实如果只是子组件需要使用上下文的数据直接通过props传值就行,如果是在需要父组件透传数据的场景下用useContext比较好,像主题切换、国际化等等场景下。

useMome

useMomeVuecomputed很是类似,不同点就是第二个参数需要写下const cachedValue = useMemo(calculateValue, dependencies)

  • calculateValue 要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。
  • dependencies 所有在 calculateValue 函数中使用的响应式变量组成的数组。
jsx 复制代码
import React, { useState, useMemo} from "react";

const UseMomePage = () => {
    const [count, setCount] = useState(1)
    const [count1, setCount1] = useState(2)

    const doubleCount = useMemo(() => {
      return count * count1
    }, [count, count1])

    return (
      <div>
        <h1>useMemo 使用示例</h1>
        <button onClick={() => setCount(count + 1)}>点击更改count+1</button>
        <button onClick={() => setCount1(count1 + 1)}>点击更改count1+1</button>

        <p>当前count值:{count}</p>
        <p>当前count1值:{count1}</p>
        <p>当前useMemo后doubleCount值:{doubleCount}</p>
      </div>
    )
}

export default UseMomePage

像前面的示例中如果第二个参数不仅仅传递[count],确实只能监听到count更新才能从新计算了,但是代码中会有警告(React Hook usemo缺少一个依赖项:'count1'。要么包含它,要么删除依赖项) ,因为count1没有被监听到。

注意为了保证calculateValue是一个纯函数,react在开发模式下会默认触发两次该函数,为你避免使用错误,及时发现bug

useCallback

useCallbackuseMemo很像,不同点是useCallback返回的是一个函数,useMemo返回的是一个值。

在下面一种场景中使用useCallback可以记忆一下setCount避免重复更新子组件

jsx 复制代码
import React, { useState, useCallback } from 'react';

// 子组件
const ChildComponent = React.memo(({ onIncrement }) => {
  console.log('子组件重新渲染!!');

  return (
    <button onClick={onIncrement}>点击+1</button>
  );
});

// 父组件
function ParentComponent() {
  const [count, setCount] = useState(0);

  // 使用 useCallback 包裹回调函数
  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []); // 依赖数组为空,表示回调函数只在组件挂载时创建一次

  return (
    <div>
      <p>Count: {count}</p>
      <ChildComponent onIncrement={increment} />
    </div>
  );
}

export default ParentComponent;

useCallbackuseMemomome函数都是React提供的用于优化性能的,合理的运用会有性能显著提升

`

react proxy 代理

vue中是直接更改vue.config.js中的devServer.proxy去配置代理的,其实内部也是使用的http-proxy-middleware这个插件实现的,感兴趣可以去看看如何用代理http、https服务的。

react中需要手动下载这个插件yarn add http-proxy-middleware,然后在src目录下创建setupProxy.js用法跟vue的一样。

js 复制代码
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:5000',
      changeOrigin: true,
      pathRewrite: {
        '^/api': '' // 去掉请求路径中的 `/api` 前缀
      }
    })
  );

  app.use(
    '/api2',
    createProxyMiddleware({
      target: 'http://localhost:5001',
      changeOrigin: true,
      pathRewrite: {
        '^/api2': ''
      }
    })
  );
};

React使用TS

因为react支持ts确实比较好,大多数公司都会选择react + ts开发,我觉得如果你都学到这里了ts也可以简单学学,都是一些基础的东西。

在我刚写ts的时候,领导告诉我说ts是一种思想,你可以把所有的代码都有提示,鼠标放上去就知道怎么穿参数,以及对象引用都可以有代码提示,写代码不要太爽。

刚学初期只要保证不是用any,保证每个代码都是有类型提示的就行,类型体操可以等你写熟练了后,其实就跟你写编程一样,把类型定义写的更加抽象了(这里建议适当,不要太抽象)

下一期我会单独出一篇ts的相关应用,就不在这里多说了

总结

咱们从路由->组件JSX编写->组件传值->插槽->样式编写->常用Hooks的相关细节应用->代理等各个方面做了详细的讲解。以上全部吸收后,写react完全没有问腿, 希望会对你有所帮助。

本篇所有的代码链接: 代码仓库

本篇所有的DEMO在线预览链接:在线预览

相关截图:

相关推荐
GDAL2 分钟前
better-sqlite3之exec方法
javascript·sqlite
匹马夕阳43 分钟前
基于Canvas和和原生JS实现俄罗斯方块小游戏
javascript·canva可画
m0_6161884944 分钟前
Vue3 中 Computed 用法
前端·javascript·vue.js
六个点1 小时前
图片懒加载与预加载的实现
前端·javascript·面试
weixin_460783871 小时前
Flutter解决TabBar顶部页面切换导致页面重载问题
android·javascript·flutter
Patrick_Wilson1 小时前
🔥【全网首篇】30分钟带你从0到1搭建基于Lynx的跨端开发环境
前端·react.js·前端框架
逍遥客.1 小时前
uniapp对接打印机和电子秤
javascript·vue.js·uni-app
小沙盒1 小时前
godot在_process()函数实现非阻塞延时触发逻辑
javascript·游戏引擎·godot
Moment1 小时前
前端 社招 面筋分享:前端两年都问些啥 ❓️❓️❓️
前端·javascript·面试
Moment1 小时前
一坤时学习 TS 中的装饰器,让你写 NestJS 不再手软 😏😏😏
前端·javascript·面试