Играя с реактивностью Vue 3, я заметил поведение, которое не мог объяснить.
Я создал ref примитива. Проверка isRef на нем возвращает явно true. Но при передаче дочернему компоненту как prop, isRef(), а также isReactive() возвращают false. Почему ?
Кроме того, даже если они оба возвращают false, наблюдатель этого реквизита, который я добавил в дочерний компонент, срабатывает, если значение изменяется. Как это может сработать, если отслеживаемое значение не является ни ref, ни reactive?
Вот фрагмент кода для родительского и дочернего элементов:
Родитель.vue
let foo = ref(0);
function changeFoo() {
foo.value++;
}
console.info("parent check is reactive?", isReactive(foo)); // retruns false
console.info("parent check is ref?", isRef(foo)); // retruns true
watch(foo, (value, oldValue) => {
console.info("foo changing from", oldValue, "to ", value);
});
Child.vue
const props = defineProps<{
foo: number;
}>();
console.info("child check is reactive?",isReactive(props), isReactive(props.foo)); // returns true false
console.info("child check is ref ?",isRef(props), isRef(props.foo)); // returns false false // Question 1: Why props.foo is not a Ref ?
watch(props, (value, oldValue) => {
console.info("props changing from", oldValue, " to ", value);
});
watch(()=>props.foo, (value, oldValue) => {
// Question 2: Why this Watcher detects changes of "foo" without it being a ref nor reactive ?
console.info("props.foo changing from ", oldValue, " to ", value);
});
И ссылка на Vue SFC Playground здесь
Бонусный вопрос:
Когда foo передается составному объекту, используемому в дочернем компоненте, наблюдатель внутри составного объекта не запускается, если мы не передаем foo через toRef, но нам не нужен этот дополнительный шаг, если foo является ref/реактивным объектом. Зачем примитивам нужен этот шаг добавления?





Но при передаче дочернему компоненту в качестве свойства isRef(), а также isReactive() возвращают false.
Вы рассказываете здесь только половину истории, как на самом деле указывает ваш собственный комментарий:
console.info("child check is reactive?",isReactive(props), isReactive(props.foo)); // returns true false
isReactive(props) возвращает true, потому что весь объект props (который обертывает foo) является реактивным. Любое обновление, которое родитель вносит в foo, передается дочернему элементу как обновленный props объект. Это правда, что props.foo не является реф/реактивом, потому что в этом нет необходимости. Пока props активен, props.foo будет обновляться
Наблюдатель может активироваться при изменении props.foo, потому что вы на самом деле используете специальный синтаксис, когда вы передаете «геттер» часам, специально предназначенным для наблюдения за свойством реактивного объекта (в вашем случае: props). В Vue docs есть пример, в котором говорится то же самое.
Если вам когда-либо нужно было присвоить props.foo свою собственную реактивную переменную, скажем, чтобы перейти к компонуемой, вот где можно использовать toRef.
// reactive but not synced with props.foo
const newFoo = ref(props.foo)
// reactive AND synced with props.foo
const newFoo = toRef(props, 'foo')
Как я указал в комментариях выше, если вы создадите новую переменную только с ref, она будет реактивной, но не будет синхронизирована со своим исходным свойством, то есть первая newFoo не будет реактивно обновляться при обновлении props.foo, но второй newFoo будет. Это также хорошо объяснено в Vue docs
если foo не является реактивным в родительском элементе, то родитель не будет знать, что нужно реактивно обновить дочернюю опору на основе обновления foo. props.foo реактивен в дочернем, но ждет реактивных обновлений от родителя, которые никогда не придут.
Этот ответ мне очень помог. Отмечу как правильный. Но просто нет ответа на мой первый вопрос: «Вопрос 1: почему props.foo не является ссылкой?» и ответ заключается в том, что ссылки, переданные в качестве реквизита, разворачиваются. это эквивалент передачи foo.value, поэтому в моем случае простой необработанный примитив передается дочернему компоненту, а не объекту ref, поэтому isRef(props.foo) является ложным внутри дочернего элемента
Простой ответ заключается в том, что в дочернем элементе «props.foo» является реактивным при использовании в настройке/скрипте (т.е. не в шаблоне, согласно моим играм), но «foo» не является реактивным, если реквизит деструктурирован (т.е. const {foo} = props).
Но вы должны сделать это с помощью функции toRef ( const foo = toRef(props,'foo')), чтобы быть уверенным, ИЛИ сделать весь объект реквизита реактивным с помощью toRefs (const reactiveProp = toRefs(props)), а затем обратиться к нему с помощью reactiveProp.foo (и он все равно будет реактивным, даже если вы его деструктурируете) ИЛИ используйте computed например const reactiveFoo = computed(()=>props.foo).
Вы можете поэкспериментировать (и посмотреть мои заметки с комментариями здесь Площадка реактивности Vue Props).
Некоторое время меня это расстраивало, потому что это не похоже на React.js (который я использовал в течение долгого времени), реквизит реактивный (даже если он деструктурирован). Смотрите тест здесь Реактивность React Props
Спасибо за ответ. Я уверен, что я ближе к пониманию поведения. Если свойство всегда реактивно, и поэтому обнаруживаются изменения foo, зачем нам делать foo ссылкой в родительском элементе? Мы можем объявить его как необработанный let foo = 0 и передать его как свойство, которое будет реактивным. Я пробовал это, но это не сработало. Наблюдатель не запускается после изменения foo в родительском элементе. Это кажется логичным, поскольку foo не является ref, но я не могу понять, почему он отличается от моего первого примера, поскольку они оба являются isRef false, и оба являются свойствами реактивного объекта 'реквизит'