likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 19

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

1. 组件风格

1.1 React

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

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

// 类组件
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

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

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

2.2 渲染函数

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

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

React

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 函数

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 状态, 并进行状态数据的读写操作。

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

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

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 将返回相同的引用。


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

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

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

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 差不多

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
// 使用 forwardRef 定义组件,可以传递 ref 参数
const MyInput = forwardRef(function MyInput(props, ref) {
  // ...
});
  • 传递 ref 引用,用于操作子代组件的DOM。Vue 中直接获取组件实例的对应的 ref 变量即可
  • 配合 useImperativeHandle 选择暴露子组件的方法,类似 Vue3 中的 expose
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

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

// 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 ,然后返回 下一个状态

const [state, dispatch] = useReducer(reducer, initState);

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 会增加复杂度,收益不明显。

多看文档:

转载自:https://juejin.cn/post/7280740005570920448
评论
请登录