写给 vuer 的 react 的快速上手指南

本文对比 React16.8+ 和 Vue3,了解 React 的基本用法,帮助 Vue 使用者快速上手 React 开发。

1. 组件风格

1.1 React

React中提供了两种风格的组件:类组件和函数组件

React 类式组件已过时,初学简单了解即可,👉going-all-in-on-modern-react-with-hooks

jsx 复制代码
// 类组件
class Greeting extends Component {
  // 类组件render方法是必须的
  render() {
    return <h1>Hello, {this.props.name}!</h1>;
  }
}

// 函数组件
function Greeting(props) {
  return <h1>Hello, {props.name}</h1>;
}

函数组件相比较类组件,依靠Hooks:

  • 更简洁
  • 便于逻辑的拆分复用
  • 没有 this 指向不明问题

2.2 Vue

Options API:Vue2 时期的 Class风格,Vue3也支持,但 Vue3 更推荐使用类似 React Hooks 的 Composition API

html 复制代码
<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    increment() {
      this.count++;
    },
  },
};
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

Composition API:Vue3 借鉴 Hooks 的思想,基于响应式系统实现了组合式API,能更好地组织代码和逻辑重用。

注意 Composition API 只是一种风格,通过 setup 选项/或者 <script setup> 语法糖定义;两种 API 风格都是基于同一个底层系统;

  • Vue3 中选项式 API 是在组合式 API 的基础上实现的👉 applyOptions
  • Vue2.7 也支持了 Composition API,👉src/V3,使用上有一些限制
html 复制代码
<script setup>
import { ref } from "vue";

const count = ref(0);

function increment() {
  count.value++;
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

script setup

组合式API

组合式 API 常见问答

2. JSX & template

JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。

JSX 和 template 对比

  • JSX:拥有 JS 的能力,更纯粹,更灵活;类型支持好

  • template:

    • 优点:可读性好,更易上手,适合初学者;vue通过编译可做优化;
    • 缺点:引入模板语法、模板指令等新概念;类型支持不好;

2.1 JSX 使用

html-to-jsx

先简单介绍一下 JSX 的使用,对于 Vue 使用者来说刚上手可能有些别扭,需要适应。

  1. 属性要变驼峰
jsx 复制代码
 return <img className="avatar" />;
  1. 变量要用{}
jsx 复制代码
return (
  <h1
    style={{
      color: user.color,
    }}
  >
    {/* 单行注释 */}
    {user.name}
  </h1>
);
// style={{}}不是特殊语法,里面的花括号代表style对象
  1. 条件渲染:基于 JS 的能力
jsx 复制代码
  return <div>{isLoggedIn ? <AdminPanel /> : <LoginForm />}</div>;
  
  return <div>{isLoggedIn && <AdminPanel />}</div>;
  1. 列表渲染:基于 JS 的能力,使用map
jsx 复制代码
const listItems = products.map((product) => (
  <li key={product.id}>{product.title}</li>
));

return <ul>{listItems}</ul>;
  1. 声明事件
jsx 复制代码
return <button onClick={handleClick}>Click me</button>;
  1. 组件返回多个元素,React.Fragment的语法糖,vue2不支持,vue3支持
jsx 复制代码
  return (
    <>
      <div>1</div>
      <div>2</div>
    </>
  );

2.2 渲染函数

React 和 Vue 都是基于虚拟 DOM 设计的,JSX 或 template 都会编译成 render 函数,返回 VNode

JSX 这种属于声明式,而直接使用 createElement 属于编程式。

React

jsx 复制代码
import { createElement } from 'react';
// 函数组件直接return,类组件需要定义render函数
function Greeting({ name }) {
  return createElement(
    'h1',
    { className: 'greeting' },
    'Hello ',
    createElement('i', null, name),
    '. Welcome!'
  );
}

// 等价于JSX:
function Greeting({ name }) {
  return (
    <h1 className="greeting">
      Hello <i>{name}</i>. Welcome!
    </h1>
  );
}

React 通过 createElement 创建 VNode,React 通过 Babel 插件将 JSX 语法编译成为 createElement 的代码,JSX 可以看做语法糖。

createElement(type, props, ...children)

babeljs.io/repl

Vue

Vue3 提供了 h 函数用于创建 VNode,Vue2 略有差异;Vue 是通过 Compiler 将 template 编译成 render 函数

jsx 复制代码
import { h, ref } from 'vue'
export default {
  setup() {
    const count = ref(0)
    // setup 可以直接返回一个渲染函数,或者通过render选项定义
    return () => h('div', count.value)
  }
}

Vue3文档-渲染函数 & JSX

Vue2文档-渲染函数 & JSX

3. React Hooks

React Hooks 只能在函数组件中使用,且只能在组件函数的最顶层(条件语句、循环语句或其他嵌套函数内都不行)调用。

3.1 useState

让函数组件也可以有 state 状态, 并进行状态数据的读写操作。

jsx 复制代码
     const [count, setCount] = useState(0);
  //     -------  ----------          -----
  //        ^         ^                 ^
  //        |         |                 |
  //    state变量  state setter函数        state初始值
  • setter 函数的功能,更新 state 变量并触发 React 重新渲染组件。每次更新 state 都会重新生成一份快照,函数(组件)会根据新的 state 生成新的 VNode,然后更新视图。
  • setter 函数分两种情况:
jsx 复制代码
const [count, setCount] = useState(0);

// 传入普通值,由于拿到当前的 count:0,并不会修改当前的count
const add1 = () => {
  setCount(count + 1) // => count = 0 + 1 = 1
  setCount(count + 1) // => count = 0 + 1 = 1
  setCount(count + 1) // => count = 0 + 1 = 1 
};

// 传入函数`n => n + 1`,可以拿到更新后的 count,相当于把更新函数加入了队列
const add2 = () => {
  setCount(n => n + 1) // => count = 0 + 1 = 1
  setCount(n => n + 1) // => count = 1 + 1 = 2 
  setCount(n => n + 1) // => count = 2 + 1 = 3
};

React 的强调的是数据不可变(immutable),而 Vue 核心是响应式(数据可变)

3.2 useEffect & useLayoutEffect

副作用就是让一个函数不再是纯函数的各类操作,比如请求数据,DOM操作等

  • 可用于模拟类组件中的生命周期钩子
  • 副作用执行

useEffect

jsx 复制代码
useEffect(() => {/* 省略 */; return () => {/* 省略 */};}, [status]);
//        ------------------------------------------     -------
//                       ^         -----------------        ^
//                       |                 ^                |
//                  副作用回调函数         清除函数         依赖值数组
  • 如果不传第二个参数,则每次渲染都会调用

  • 如果依赖值数组为空,则副作用回调函数会在组件挂载后执行一次(可以访问 DOM)。可对标 Vue 中的onMounted

    • 如果 return 了函数,则为清除函数,会在组件被卸载之前执行。可对标 Vue 中的onUnmouted
  • 如果依赖值数组不为空,其中的值如果发生变化(shallow compare),副作用回调函数会重新执行。

    • 依赖值可以使用 props、state、context 的变量

Vue 中 watchEffect 会自动追踪依赖,且首次会执行。

useLayoutEffect

使用方法和 useEffect 相同,但它会在所有DOM变更之后同步 调用回调函数,和类组件的componentDidMount 时机相同

而 useEffect 的回调函数会在组件渲染到屏幕之后延迟执行。

3.3 useContext

有点像 Vue 中的 provide 和 inject

如果组件层级比较深,使用 props 逐级透传不方便,可使用 createContextuseContext

jsx 复制代码
import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  )
}

// Button 是 MyApp 的后代组件
function Button() {
  const theme = useContext(ThemeContext);
  return <button className={theme} />;
}

3.4 useRef & useImperativeHandle

使用useState定义的变量,每次渲染的都是一个 state 快照,是相互独立的。如何让组件在重新渲染之间保留数据,则需要使用 useRef,其定义的变量是可变的,也不会触发重新渲染。

不要在渲染过程中读取或写入 ref.current

一般用于保存DOM或任意数据,重新渲染 useRef 将返回相同的引用。

jsx 复制代码
const Component = () => {
  const myRef = useRef(null);
  //    -----          ----
  //      ^              ^
  //      |              |
  //   可变ref对象     可变ref对象current属性初始值

  // 读取可变值
  const value = myRef.current;
  // 更新可变值
  myRef.current = newValue;
  return (<div></div>);
};
  • 存储数据

Vue 是本来数据就是可变的,可以维持状态,不需要这个

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

export default function Counter() {
  let ref = useRef(0);

  function handleClick() {
    ref.current = ref.current + 1;
  }

  return (
    <button onClick={handleClick}>
      Click me!
    </button>
  );
}
  • 操作 DOM / 组件

这里就和 Vue 中 使用 ref 差不多

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

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        Focus the input
      </button>
    </>
  );
  • forwardRef
jsx 复制代码
// 使用 forwardRef 定义组件,可以传递 ref 参数
const MyInput = forwardRef(function MyInput(props, ref) {
  // ...
});
  • 传递 ref 引用,用于操作子代组件的DOM。Vue 中直接获取组件实例的对应的 ref 变量即可
  • 配合 useImperativeHandle 选择暴露子组件的方法,类似 Vue3 中的 expose
jsx 复制代码
import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  useImperativeHandle(ref, () => {
    return {
      // ... your methods ...
    };
  }, []);

  return <input {...props} />;
});

Imperative: 必要

3.5 useCallback & useMemo

优化性能使用,可以减少不必要的渲染,不必过早的性能优化

jsx 复制代码
// useCallback 是通过 useMemo 实现的
function useCallback(callback,deps ) { return useMemo(() => callback, deps); }

const memoizedFunc = useCallback(() => {/*省略*/}, [a, b]);
//    ------------               ---------------   -----
//         ^                            ^            ^
//         |                            |            |
//   记忆化的回调函数                   回调函数      依赖值数组


const memoizedFunc = useMemo(() => () => {/*省略*/}, [a, b]);
//    ------------           ---------------------   -----
//       ^                      ^  ---------------      ^
//       |                      |         ^             |
// 工厂函数返回的回调函数        工厂函数   回调函数        依赖值数组
  • useMemo

useMemo 的功能是为工厂函数返回一个记忆化的计算值,在两次渲染之间,只有依赖值数组中的依赖值有变化时,该 Hook 才会调用工厂函数重新计算,将新的返回值记忆化并返回给组件。

useMemo 最重要的使用场景,是将执行成本较高的计算结果存入缓存,通过减少重复计算来提升组件性能。

useMemo 可类比 Vue 中的 computed

  • useCallback

如果需要缓存函数则使用 useCallback,少一层箭头函数,更方便使用。

useCallback ,它会把作为第一个参数的回调函数返回给组件,只要第二个参数依赖值数组的依赖项不改变,它就会保证一直返回同一个回调函数(引用),而不是新建一个函数,这也保证了回调函数的闭包也是不变的;相反,当依赖项改变时, useCallback 才会更新回调函数及其闭包。

3.6 useReducer

来源 redux 的设计思想,接受 目前的状态action ,然后返回 下一个状态

jsx 复制代码
const [state, dispatch] = useReducer(reducer, initState);
jsx 复制代码
const initialState = { count: 0 };

// 返回新的 state
function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  // useReducer 会根 据dispatch的action,返回新的state,并触发rerender
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </>
  );
}

4. 其他

  • 为什么 vue3 没有采用时间切片进行 diff

Why remove time slicing from vue3?

总结就是:Vue由于响应式和编译优化,性能已经足够好,引入 Fiber 会增加复杂度,收益不明显。

多看文档:

相关推荐
腾讯TNTWeb前端团队5 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰8 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪8 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy9 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom10 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom10 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom10 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom10 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom10 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试