react Notes

114 阅读6分钟

-- 前端演进:

  1. 静态页 - ajax - Jquery(解决不同浏览器兼容);
  2. 需求简化代码、提高维护性 - 设计模式 - MVC;
  3. 工程化 - 模块化:angular2、Vue、React;

-- React 设计哲学 - 简洁之美

  1. 单项数据流:数据页面绑定、单项渲染 - ui = f(x);
  2. 虚拟DOM;
  3. 组件化(一致性、方便协作);
  4. 可预测:编写可预测、符合开发者习惯;
  5. 简化模型:JSX、界面更新、单项数据流;
  6. 衍生:React Natice(一次学习、多处书写)

-- React、Vue 区别

  • 相同点:支持组件化、数据驱动视图、使用vdom
  • 不同点:React使用jsxVue模板拥抱htmlReact函数时编程、Vue声明式编程

版本变化

v16: 痛不欲生,更新非常快,大版本不兼容、v16.3 / 8 新特性特别多;

v17:

  1. 采取渐近升级和 angular2-13 类似;
  2. 事件委托机制改变,更改成根DOM容器;
  3. 删除事件池;
  4. useEffect 清理操作改为异步(过去同步);
  5. jsx不可返回 undefined;
  6. 移除 react native web 不需要的内部组件;
  7. 破坏性改变:onFocus onBlur;

v18: 并发渲染; 新 Susense 组件; 新特性 startTransition(防抖)

v19(无缝升级) actions 简化异步操作,自动处理状态和错误; useOptimistic / useActionState; 服务器组件和ServerActions; 表单改进; 支持样式表和异步脚本管理,提升资源加载灵活性

jsx

--

React Element

const App = () =>{
  return (
    <div className='hello'>app</div>
  )
}
console.log(App()) // 如下

jsx 被转换为 React.createElement 函数

const App = () => {
	return React.createElement('div', {
		className:'hello'
	}, 'App')
}
//  最终
{
  '$$typeof': Symbol(react.element),
  "key": null,
  "props": { "children": "app" }, // 文字
  "ref": null,
  "type": "div" // 类型
}

React Component,组件实例 提供 this 变量,追踪组件,调用组件属性和方法(即所有实例会有生命周期方法或者组件状态)

import React from 'react'
export default class App extends React.Component {
  render() {
    return <div>App</div>
  }
}

转换为:

{
  '$$typeof': Symbol(react.element),
  "key": null,
  "props": {}, // 文字
  "ref": null,
  "type": () => { ... }
}

结论:

函数式组件直接调用并附带 Props;
类组件:创建实例再调用 render( React.Component 等价于DOM节点,也可以代表组件实例 );

setState

不可直接修改 state,必须使用不可变值(不能直接操作 state 中的值即对源数据修改)

increase = () => {
  // this.state.count++ // 错误
  this.setState({
    count: this.state.count + 1 // 正确
  })
}

不能直接对数组进行 push pop splice 等,违反不可变值

// 正确方法
this.setState({
    list1: this.state.list1.concat(100), 
    list2: [...this.state.list2, 100], 
    list3: this.state.list3.slice(0, 3), 
    list4: this.state.list4.filter(item => item > 100),
    list5: list5Copy 
})

不能直接对 obj 进行属性设置,违反不可变值

// 正确方法
this.setState({
    obj1: Object.assign({}, this.state.obj1, {a: 100}),
    obj2: {...this.state.obj2, a: 100}
})

在版本 v16 / v17 自身的合成事件中为异步

this.setState(
  {
    count: this.state.count + 1
  },
  () => {
    console.log(this.state.count) // 回调获取最新值
  }
)
console.log(this.state.count) // 异步获取不到最新值

但版本 v16 / v17 dom 事件和 web api 是同步(V18 版本全部为异步 !!!

// 1
setTimeout(() => {
  this.setState({
    count: this.state.count + 1
  })
  console.log(this.state.count) // 获取到最新值
}, 0)

// 2
componentDidMount() {
    document.body.addEventListener('click', this.bodyClickHandler)
}
componentWillUnmount() {
   document.body.removeEventListener('click', this.bodyClickHandler)
}
bodyClickHandler = () => {
    this.setState({
        count: this.state.count + 1
    })
    console.log(this.state.count) // 同步获取到最新值
}

当执行 setState 时,内部会定义 isBatchingUpdates = true,类似于开关,执行完毕为 false

increase = () =>{
  // 开始:处于 batchedUpdates 状态
  // isBatchedUpdates = true
  this.setState({
    count: this.state.count + 1
  })
  // 结束
  // isBatchedUpdates = false
}

命中异步

increase = () => {
  // 开始:处于 batchedUpdates 状态
  // isBatchedUpdates = true
  setTimeout(() => {
    // 此时 isBatchedUpdates 是 false
    this.setState({
      count: this.state.count + 1
    })
  })
  // 结束
  // isBatchedUpdates = false
}

setState 在更新前会被合并,因为异步更新 count 本身并未改变,每次读取时都为同一结果

// 只会 +1
increase = () => {
  this.setState({
    count: this.state.count + 1
  })
  this.setState({
    count: this.state.count + 1
  })
}

函数式不会合并

increase = () => {
  this.setState((prevState, props) => {
    return {
      count: prevState.count + 1
    }
  })
  this.setState((prevState, props) => {
    return {
      count: prevState.count + 1
    }
  })
}

总结

<= react 17 :React 组件事件异步更新,DOM事件、定时器同步更新不合并 statesetTimeout 会将 React 组件事件进行批处理

>= react 18 皆为异步更新,所有事件都自动 批处理(Automatic Batching)(React的渲染过程并不立即响应所有的状态变化,而是将它们放入一个事件队列中,当事件队列积累了一定数量的变化或者浏览器空闲时,才会一次性批量处理这些变化。)

合成事件

本质是 React 封装的SyntheticEvent组合事件(模拟出DOM事件所有能力,合成事件机制利于更好的兼容性和跨平台,避免频发解绑方便事件统一管理);

  • event.target 事件发生元素(指向当前元素)
  • event.currentTarget 事件绑定元素也指向当前元素,但其实是假象
  • event.nativeEvent 是原生事件对象(Vue 也是原生事件对象)
  • event.nativeEvent.target 指向当前元素,即当前元素触发
  • 通过 event.nativeEvent.currentTarget可知:react16所有的事件都被挂 document (减少内存消耗)、react17/18指向 root

在这里插入图片描述

为什么使用合成事件机制:

兼容和跨平台、挂载到顶层减少内存消耗、避免频繁解绑、方便事件的统一管理

在这里插入图片描述

transaction 事务机制

以上在执行 increasse 函数,会先执行 initalize ,再执行函数本身,最后执行 close ,此为事务机制; 如图所示:

在这里插入图片描述 简单使用伪代码理解:

transaction.initalize = function(){}
transaction.close = function(){}
function method(){}
transaction.perform(method)

生命周期

周期图示:projects.wojtekmaj.pl/react-lifec…

在这里插入图片描述

注意: getDerivedStateFromProps(替代componentWillReceiveProps)用于在组件接收新的props时更新其状态,它在组件挂载和更新时都会被调用,可以根据新的props和当前的state来计算并返回一个新的state,这个方法的返回值将会被传递给组件的render方法,从而更新组件的UI。

react-router-dom

安装

npm install react-router-dom
yarn add react-router-dom
pnpm add react-router-dom
  • BrowserRouter 使用 HTML5 提供的历史 API 来保持 UI 与 URL 同步;
  • HashRouter 通过在URL中添加哈希片段来工作(#),从而不会触发页面刷新并能绕过某些服务器限制;
  • MemoryRouter 不依赖于实际的位置历史记录;相反,它是完全存在于内存中的位置管理器 (服务端渲染);
  • Route 负责匹配当前请求的 URL 并决定是否渲染对应组件,当请求路径与指定路径相匹配时,关联组件将会被渲染到页面上;
  • Routes 则是一个容器组件,用来包裹多个 Route 实例,其主要作用在于优化性能以及提供更合理的嵌套结构支持,替代旧版中的 Switch 。
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
<BrowserRouter>
	<Routes>
	   <Route path='/' element={<App />}></Route>
	   <Route path='/demo' element={<Demo />}></Route>
           <Route path='*' element={ <404 /> }></Route>
	</Routes>
</BrowserRouter>

NavLink、Link

本质 <a> 标签,to = '..' 表示返回上一级

组件含义
NavLink自带 active 属性(activeClassName)
Link创建可点击链接,这些链接会导航到不同的 URL 而不会重新加载页面

重定向

<Route path='/demo' element={ <Navigate to={'/'} /> }></Route>
function navigate() {
  return <h2>{ <Navigate to={'/'} /> }</h2>
}

useNavigate

const navigate = useNavigate();
navigate('/home') // 通过事件进行跳转

函数式 useRoutes

router.ts

function Router(){
  const routes = useRoutes([
      {
        path: "/",
        element: <App />,
      },
      {
        path: "*",
        element: <NotFound />
      },
  ]);
}
export default Router;

main.ts

import BaseRouter from './router'
import { BrowserRouter } from 'react-router-dom'

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <BrowserRouter>
    <BaseRouter />
  </BrowserRouter>
)

create

createBrowserRouter、createHashRouter

import { createBrowserRouter } from "react-router-dom";

// 创建路由
const router = createBrowserRouter([
  {
    path: "/",
    element: <Home />,
  },
  {
    path: "*",
    element: <NotFound />
  },
]);

App.tsx

import { RouterProvider  } from 'react-router-dom'
<RouterProvider router={router} />

基础路由

配置根地址:http://localhost:8080/app

const router = createBrowserRouter(
  [
    {
      path: '/',
      element: <App />
    }
  ],
  {
    basename: '/app'
  }
)

动态路由

import { useParams } from "react-router-dom";
function Order() {
  const params = useParams();
  return <span>商品ID:{params.goodsId}</span>;
}
// 一级
{
    path: '/order/:orderId',
    element: <Order/>
}
// 二级
{
    path: '/goods/:goodsId/order/:orderId',
    element: <Order/>
}

嵌套路由

Outlet(相当于 Vue 里面的 route-view 组件)展示子组件

// 父组件定义
function Goods() {
  return (
    <div>
      <h2>商品主页</h2>
      <Outlet />
    </div>
  )
}
{
  path: '/goods',
  element: <Goods />,
  children: [
    {
      path: 'list',
      element: (
        <div>
          <p>商品一</p>
          <p>商品二</p>
        </div>
      )
    },
  ]
}

加载器

使用:createBrowserRouter、createMemoryRouter、createHashRouter、createStaticRouter Loader、useLoaderData:数据加载器、优先加载 loader 再加载 element

{
  path: '/order/:id',
  element: <Order />,
  loader: orderLoader
}
function orderLoader({ params }: any) {
  return params.id;
}
// 登录拦截
function orderLoader({ params }: any) {
  if (!sessionStorage.token) return redirect('/login')
  return {
    token: sessionStorage.token
  }
}
// 可以直接执行接口,请求 json 文件
function orderLoader({ params }: any) {
  if (!sessionStorage.token) return redirect('/login')
  return fetch(`/${params.id}.json`)
}

useActionData

文档:reactrouter.com/en/main/hoo…