Vue Composition API против React Hooks

Vue.js
Vue Composition API против React Hooks

Лиан Пэнфэй, главный инженер группы облачных сервисов WeDoctor, понимает мир технологий с «предубеждениями».

Сцены

Сначала поймите, что такое хук.Принимая во внимание введение React, его определение таково:

Это позволяет вам «подключаться» к состоянию React и функциям жизненного цикла в функциональных компонентах без написания класса.

Для нового API Vue для написания компонентов Vue: Composition API RFC эффект аналогичен, поэтому мы также можем называть его Vue Hooks, например React.

  • API вдохновлен React Hooks.
  • Но есть несколько интересных отличий, которые позволяют обойти некоторые проблемы React.

Значение эпохи Крюка

Фреймворк служит бизнесу, и проблема, которую трудно избежать в бизнесе, заключается в следующем: повторное использование логики, одна и та же функция, один и тот же компонент в разных случаях, иногда нам приходится писать 2+ раза, чтобы избежать связывания, затем основные фреймворки придумали некоторые методы, такие как minix, render props, компоненты высокого порядка и т. д., для достижения логического повторного использования, но есть некоторые дополнительные проблемы.

  • Существуют неявные зависимости между minix и компонентами, которые могут конфликтовать. Имеет тенденцию добавлять больше состояний, снижая предсказуемость приложения.
  • Компоненты более высокого порядка оборачивают вложенные компоненты в несколько слоев, что увеличивает сложность и стоимость понимания, а также является черным ящиком для внешнего слоя.
  • Реквизиты рендеринга громоздки в использовании, не просты в обслуживании, размер кода слишком велик, и их также легко вкладывать слишком глубоко.
  • ...

Появление хука стало эпохальным, благодаря извлечению функций реализована внутренняя инкапсуляция сложной логики:

  • повторное использование логического кода
  • Уменьшенный размер кода
  • не беспокойтесь об этом

React Hooks

React Hooks позволяют «подключать» функции React, такие как состояние компонента и обработка побочных эффектов. Хуки можно использовать только в функциональных компонентах, и они позволяют нам переносить состояние, обработку побочных эффектов и многое другое в компоненты без необходимости создавать классы.

Стратегия внедрения основной команды React не противоречит классовым компонентам, поэтому вы можете обновить свою версию React, начать экспериментировать с хуками в новых компонентах и ​​оставить существующие компоненты без изменений.

пример:

import React, { useState, useEffect } from "React";

const NoteForm = ({ onNoteSent }) => {
  const [currentNote, setCurrentNote] = useState("");
  useEffect(() => {
    console.log(`Current note: ${currentNote}`);
  });
  return (
    <form
      onSubmit={e => {
        onNoteSent(currentNote);
        setCurrentNote("");
        e.preventDefault();
      }}
    >
      <label>
        <span>Note: </span>
        <input
          value={currentNote}
          onChange={e => {
            const val = e.target.value && e.target.value.toUpperCase()[0];
            const validNotes = ["A", "B", "C", "D", "E", "F", "G"];
            setCurrentNote(validNotes.includes(val) ? val : "");
          }}
        />
      </label>
      <button type="submit">Send</button>
    </form>
  );
};
  • useState и useEffect — некоторые примеры хуков React, которые позволяют добавлять состояние и запускать побочные эффекты в функциональные компоненты.
  • Хуков гораздо больше, даже один можно настроить. Хуки открывают новые возможности для повторного использования кода и расширяемости.

Vue Composition API

Vue Composition API построен на новой настройке параметров компонента.setup()Предоставляет состояние, вычисленные значения, наблюдатели и хуки жизненного цикла для компонентов Vue.

API не приводит к исчезновению исходного API (теперь называемого «API на основе параметров»). Позволяет разработчикам комбинировать старые и новые API

Доступно в Vue 2.x через@vue/composition-apiПлагины пробуют новые API

пример:

<template>
  <form @submit="handleSubmit">
    <label>
      <span>Note:</span>
      <input v-model="currentNote" @input="handleNoteInput">
    </label>
    <button type="submit">Send</button>
  </form>
</template>

<script>
import { ref, watch } from "vue";
export default {
  props: ["divRef"],
  setup(props, context) {
    const currentNote = ref("");
    const handleNoteInput = e => {
      const val = e.target.value && e.target.value.toUpperCase()[0];
      const validNotes = ["A", "B", "C", "D", "E", "F", "G"];
      currentNote.value = validNotes.includes(val) ? val : "";
    };
    const handleSubmit = e => {
      context.emit("note-sent", currentNote.value);
      currentNote.value = "";
      e.preventDefault();
    };

    return {
      currentNote,
      handleNoteInput,
      handleSubmit,
    };
  }
};
</script>

разница

принцип

Нижний слой React Hook основан на реализации связанного списка.Условие вызова состоит в том, что все хуки будут выполняться последовательно каждый раз при рендеринге компонента, поэтому следующий код сообщит об ошибке

function App(){
  const [name, setName] = useState('demo');
  if(condition){
    const [val, setVal] = useState('');    
  }
}

Поскольку нижний слой представляет собой связанный список, следующий из каждого хука указывает на следующий хук, и если это приведет к неправильному порядку, что приведет к ошибке, поэтому React не позволяет использовать хук таким образом.

Vue Hook будет зарегистрирован и вызван только один раз.Vue может избежать этих неприятных проблем, потому что его ответ на данные основан на прокси, и он напрямую наблюдает за данными через прокси. В этом сценарии, если в любом месте, где данные изменяются, соответствующая функция или шаблон будут пересчитаны, что позволит избежать проблем с производительностью, с которыми может столкнуться React.

Когда данные React изменяются, это вызывает повторный рендеринг, а повторный рендеринг снова перерегистрирует хуки, поэтому начать работу с React сложнее.

Конечно, у React есть свои решения для них, такие как useCallback, useMemo и другие хуки, которые представлены на этих официальных сайтах.

Выполнение кода

В Vue «крюк» — это метод жизненного цикла.

  • Vue Composition APIиз setup()После хука beforeCreate, перед вызовом созданного хука
  • React Hooks будет запускаться каждый раз при рендеринге компонента, в то время как Vue setup() будет запускаться только один раз при создании компонента.

Поскольку хуки React запускаются несколько раз, метод рендеринга должен подчиняться определенным правилам, таким как:

Не вызывайте хуки внутри циклов, условий или вложенных функций.

// React 文档中的示例代码:
function Form() {
  // 1. Use the name state variable
  const [name, setName] = useState('Mary');

  // 2. Use an effect for persisting the form
  if (name !== '') {
    useEffect(function persistForm() {
      localStorage.setItem('formData', name);
    });
  }
  // 3. Use the surname state variable
  const [surname, setSurname] = useState('Poppins');

  // 4. Use an effect for updating the title
  useEffect(function updateTitle() {
    document.title = `${name} ${surname}`;
  });
  // ...
}

Если вы хотите запустить соответствующий побочный эффект, даже если имя пусто, вы можете просто переместить условный оператор внутри обратного вызова useEffect:

useEffect(function persistForm() {
  if (name !== '') {
    localStorage.setItem('formData', name);
  }
});

Для приведенной выше реализации Vue написан следующим образом:

export default {
  setup() {
    // 1. Use the name state variable
    const name = ref("Mary");
    // 2. Use a watcher for persisting the form
    if(name.value !== '') {
      watch(function persistForm() => {
        localStorage.setItem('formData', name.value);
      });
    }
   // 3. Use the surname state variable
   const surname = ref("Poppins");
   // 4. Use a watcher for updating the title
   watch(function updateTitle() {
     document.title = `${name.value} ${surname.value}`;
   });
  }
}

setup() в Vue будет запускаться только один раз, вы можете использовать различные функции в Composition API (реактивные, ref, вычисляемые, часы, хуки жизненного цикла и т. д.) как часть цикла или условного оператора.

Но оператор if, как и React Hooks, запускается только один раз, поэтому он не может реагировать на изменение имени, если мы не обернем его внутри обратного вызова watch.

watch(function persistForm() => {
  if(name.value !== '') {
    localStorage.setItem('formData', name.value);
  }
});

Объявление состояния

React

useStateэто основной способ для React Hooks объявить состояние

  • Вы можете передать начальное значение в качестве параметра вызова
  • Если начальное значение сложно вычислить, его также можно выразить в виде функции, которая будет выполняться только при первом рендеринге.

useState() возвращает массив, первый элемент — это состояние, а второй элемент — функция установки

const [name, setName] = useState("Mary");
const [age, setAge] = useState(25);
console.log(`${name} is ${age} years old.`);

useReducerявляется полезной альтернативой, и ее обычная форма принимает функцию редуктора в стиле Redux и начальное состояние:

const initialState = {count: 0};

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();
  }
}
const [state, dispatch] = useReducer(reducer, initialState);

dispatch({type: 'increment'}); // state 就会变为 {count: 1}

useReducer также имеет форму ленивой инициализации, передавая функцию инициализации в качестве третьего параметра.

Vue

Vue использует две основные функции для объявления состояния: ref и reactive.

ref()Возвращает реактивный объект, к внутренним значениям которого можно получить доступ через его свойство value. Вы можете использовать его для примитивных типов, но также и для объектов

const name = ref("Mary");
const age = ref(25);
watch(() => {
  console.log(`${name.value} is ${age.value} years old.`);
});

reactive()Принимает на вход только объект и возвращает ему реактивный прокси

const state = reactive({
  name: "Mary",
  age: 25,
});
watch(() => {
  console.log(`${state.name} is ${state.age} years old.`);
});

Уведомление:

  • При использовании ref вам нужно использовать свойство value для доступа к содержащемуся значению (если только в шаблоне Vue не позволяет вам его опустить)
  • При использовании реактивного помните, что если вы используете деструктурирование объекта, вы потеряете его реактивность. Поэтому вам нужно определить ссылку на объект и получить доступ к свойству состояния через него.

Подведем итог обработки с использованием этих двух функций:

  • Просто используйте ref и reactive, как объявление примитивных и объектных переменных в обычном JavaScript.
  • Всякий раз, когда используется реактивный, не забывайте использовать toRefs() при возврате реактивных объектов из функций композиции. Это снижает накладные расходы при использовании слишком большого количества ссылок.
// toRefs() 则将反应式对象转换为普通对象,该对象上的所有属性都自动转换为 ref
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })
 
  return toRefs(state)
}
 
const {foo, bar} = useFeatureX();

Как отслеживать зависимости

Хук useEffect в React позволяет запускать определенные побочные эффекты (такие как запрос данных или использование веб-API, таких как хранилище) после каждого рендеринга и выполнять некоторую очистку перед выполнением следующего обратного вызова или когда компонент размонтирован.

По умолчанию все функции, зарегистрированные с помощью useEffect, будут запускаться после каждого рендеринга, но можно определить состояние и свойства реальных зависимостей, чтобы React выполнял рендеринг, когда соответствующие зависимости не изменились (например, вызванные другими частями в состоянии ). ), чтобы пропустить выполнение хука useEffect.

// 传递一个依赖项的数组作为 useEffect Hook 的第二个参数,只有当 name 改变时才会更新 localStorage
function Form() {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins');
  useEffect(function persistForm() {
      localStorage.setItem('formData', name);
  }, [name]);

  // ...
}

По-видимому, забыв исчерпывающе объявить все зависимости в массиве зависимостей при использовании React Hooks, может легко случиться, в результате чего обратный вызов useEffect в конечном итоге «полагается и ссылается на устаревшие данные из последнего рендеринга вместо последних данных» и, таким образом, не может быть обновлен

решение:

  • eslint-plugin-React-HooksСодержит правило lint для отсутствующих зависимостей
  • useCallback 和 useMemoПараметр массива зависимостей также используется для принятия решения о том, должен ли он возвращать кэшированную (запоминаемую) версию обратного вызова или значение, соответственно, такое же, как при последнем выполнении.

В случае Vue Composition API побочные эффекты могут быть выполнены с помощью watch() в ответ на изменения состояния или свойства. Зависимости отслеживаются автоматически, а зарегистрированные функции вызываются реактивно при изменении зависимостей.

export default {
  setup() {
    const name = ref("Mary");
    const lastName = ref("Poppins");
    watch(function persistForm() => {
      localStorage.setItem('formData', name.value);
    });
  }
}

Access Component Life Cycle (Доступ к жизненному циклу компонента)

Хуки представляют собой полный сдвиг ментальной парадигмы при работе с жизненным циклом компонентов React, побочными эффектами и управлением состоянием. В документации React также говорится:

Если вы знакомы с методами жизненного цикла класса React, вы можете рассматривать useEffect Hook как набор компонентов componentDidMount, componentDidUpdate и componentWillUnmount.

useEffect(() => {
  console.log("This will only run after initial render.");
  return () => { console.log("This will only run when component will unmount."); };
}, []);

Подчеркивается, что при использовании React Hooks более принято не думать с позиции методов жизненного цикла, а учитывать, от какого состояния зависят побочные эффекты.

Vue Component APIпройти через onMounted、onUpdated 和 onBeforeUnmount:

setup() {
  onMounted(() => {
    console.log(`This will only run after initial render.`); 
  });
  onBeforeUnmount(() => {
    console.log(`This will only run when component will unmount.`);
  });
}

Таким образом, изменение ментальной модели в случае Vue больше связано с прекращением управления кодом с помощью опций компонентов (данные, вычисляемые, часы, методы, хуки жизненного цикла и т. д.) и обращением к использованию различных функций для обработки соответствующих функций.

Пользовательский код

Одна из причин, по которой команда React сосредоточилась на хуках, заключается в том, что пользовательские хуки — это лучший способ для разработчиков писать повторно используемый код вместо компонентов более высокого порядка или реквизитов рендеринга, принятых в предыдущем сообществе.

Пользовательские хуки — это обычные функции JavaScript, внутри которых используются хуки React. Одно из соглашений, которым он следует, заключается в том, что его имя должно начинаться с использования, чтобы указать, что оно используется как хук.

// custom Hook - 用于当 value 改变时向控制台打印日志
export function useDebugState(label, initialValue) {
  const [value, setValue] = useState(initialValue);
  useEffect(() => {
    console.log(`${label}: `, value);
  }, [label, value]);
  return [value, setValue];
}

// 调用 
const [name, setName] = useDebugState("Name", "Mary");

В Vue функции композиции (функции композиции) и хуки согласованы в целях извлечения логики и повторного использования. Реализуйте аналогичную функцию композиции useDebugState во Vue.

export function useDebugState(label, initialValue) {
  const state = ref(initialValue);
  watch(() => {
    console.log(`${label}: `, state.value);
  });
  return state;
}

// elsewhere:
const name = useDebugState("Name", "Mary");

Примечание. По соглашению составные функции также имеют префикс использования, как и React Hooks, чтобы указать их эффект, и, по-видимому, функция используется в setup().

Refs

И useRef в React, и ref в Vue позволяют ссылаться на дочерний компонент или элемент DOM для присоединения.

Реагировать:

const MyComponent = () => {
  const divRef = useRef(null);
  useEffect(() => {
    console.log("div: ", divRef.current)
  }, [divRef]);

  return (
    <div ref={divRef}>
      <p>My div</p>
    </div>
  )
}

Vue:

export default {
  setup() {
    const divRef = ref(null);
    onMounted(() => {
      console.log("div: ", divRef.value);
    });

    return () => (
      <div ref={divRef}>
        <p>My div</p>
      </div>
    )
  }
}

Дополнительные функции

React Hooks запускаются при каждом рендеринге, в Vue нет эквивалента вычисляемой функции. Таким образом, вы можете объявить переменную, значение которой зависит от состояния или свойства и будет указывать на последнее значение после каждого рендеринга:

const [name, setName] = useState("Mary");
const [age, setAge] = useState(25);
const description = `${name} is ${age} years old`;

В Vue setup() запускается только один раз. Следовательно, необходимо определить вычисляемые свойства, которые должны отслеживать некоторые изменения состояния и соответствующим образом обновляться:

const name = ref("Mary");
const age = ref(25);
const description = computed(() => `${name.value} is ${age.value} years old`);

Вычисление значения дорого. Вы не хотите вычислять его каждый раз при рендеринге компонента. React включает в себяuseMemo Hook:

function fibNaive(n) {
  if (n <= 1) return n;
  return fibNaive(n - 1) + fibNaive(n - 2);
}
const Fibonacci = () => {
  const [nth, setNth] = useState(1);
  const nthFibonacci = useMemo(() => fibNaive(nth), [nth]);
  return (
    <section>
      <label>
        Number:
        <input
          type="number"
          value={nth}
          onChange={e => setNth(e.target.value)}
        />
      </label>
      <p>nth Fibonacci number: {nthFibonacci}</p>
    </section>
  );
};

React рекомендует использовать useMemo для оптимизации производительности, а не кэшированное значение перед любыми изменениями зависимостей.

React advice you to use useMemo as a performance optimization and not as a guarantee that the value will remain memoized

Vue вычисляет автоматическое отслеживание зависимостей, поэтому ему не нужен массив зависимостей.

Контекст и предоставление/внедрение

Хук useContext в React можно использовать как новый способ чтения текущего значения определенного контекста. Возвращаемое значение обычно определяется ближайшим слоем<MyContext.Provider>Атрибут value дерева предков определяется

// context object
const ThemeContext = React.createContext('light');

// provider
<ThemeContext.Provider value="dark">

// consumer
const theme = useContext(ThemeContext);

Аналогичный API в Vue называетсяprovide/inject. Существовал как опция компонента в Vue 2.x, добавлена ​​пара в Composition API для использования в setup()provide 和 injectфункция:

// key to provide
const ThemeSymbol = Symbol();

// provider
provide(ThemeSymbol, ref("dark"));

// consumer
const value = inject(ThemeSymbol);

Если вы хотите продолжать реагировать, он должен явно предоставить ref / recessive, как значение

Выставление значений для контекста рендеринга

В случае Реакта

  • Весь код хуков определен в компоненте
  • и вы вернете элемент React для рендеринга в той же функции

Таким образом, у вас есть полный доступ к любому значению в области видимости, как и в любом коде JavaScript:

const Fibonacci = () => {
  const [nth, setNth] = useState(1);
  const nthFibonacci = useMemo(() => fibNaive(nth), [nth]);
  return (
    <section>
      <label>
        Number:
        <input
          type="number"
          value={nth}
          onChange={e => setNth(e.target.value)}
        />
      </label>
      <p>nth Fibonacci number: {nthFibonacci}</p>
    </section>
  );
};

В случае с Вью

  • Во-первых, определение шаблона или параметров шаблона рендеринга
  • Во-вторых, используя одно файловый компонент, верните объект из настройки (), который содержит все значения, которые вы хотите выводить на шаблон

Операторы возврата также имеют тенденцию становиться многословными из-за вероятности наличия слишком большого количества значений для раскрытия.

<template>
  <section>
    <label>
      Number:
      <input
        type="number"
        v-model="nth"
      />
    </label>
    <p>nth Fibonacci number: {{nthFibonacci}}</p>
  </section>
</template>
<script>
export default {
  setup() {
    const nth = ref(1);
    const nthFibonacci = computed(() => fibNaive(nth.value));
    return { nth, nthFibonacci };
  }
};
</script>
}

Один из способов добиться такой же чистой производительности в React — вернуть функцию рендеринга из самой setup(). Тем не менее, шаблоны — более распространенная практика в Vue, поэтому предоставление объекта, содержащего значение, — это то, с чем вы неизбежно столкнетесь при использовании Vue Composition API.

Заключение

И у React, и у Vue есть свои «сюрпризы», и между ними нет никакой разницы.С тех пор как React Hooks был представлен в 2018 году, сообщество использовало его для создания множества отличных работ, а масштабируемость пользовательских хуков также породила множество Open Source. взносы.

Vue был вдохновлен React Hooks, чтобы адаптировать его к своей собственной структуре, что и стало этимИстории успеха о том, как различные технологии принимают изменения и делятся вдохновением и решениями


Ссылаться на

  1. https://composition-api.vuejs.org/#summary
  2. https://Reactjs.org/docs/Hooks-intro.html
  3. https://dev.to/voluntadpear/comparing-React-Hooks-with-vue-composition-api-4b32