【问题标题】:Vue - Render components depending on state of parent dataVue - 根据父数据的状态渲染组件
【发布时间】:2019-04-29 08:02:04
【问题描述】:

我正在尝试根据父级 (App.vue) 中数组的状态来渲染组件。我完全不确定这是否是此用例的正确方法(Vue 新手且没有经验的程序员),所以如果您认为这不是考虑这个问题的正确方法,我很乐意接受建议。

我正在尝试构建一个包含一堆问题的疑难解答程序。每个问题都是一个组件,其数据如下所示:

data: function() {
    return {
        id: 2,
        question: "Has it worked before?",
        answer: undefined,
        requires: [
            {
                id: 1,
                answer: "Yes"
            }
        ]
    }
}

如果问题 1 的答案为“是”,则假定显示此问题。

我的问题是我不确定如何有条件地渲染我的组件。当前的方法是在响应组件时从组件发送事件,并在父组件中监听该事件。当事件触发时,父级更新一个数组,该数组包含所有已回答问题的“状态”。现在我需要从每个组件中检查这个数组,看看那里是否有已经回答的问题,如果满足正确的条件,则显示问题。

我的问题是:如何检查父级中的数据并根据它显示/隐藏我的组件?还有 - 这是个好主意还是我应该做一些不同的事情?

这里还有一些代码供参考:

App.vue

<template>
    <div id="app">
        <div class="c-troubleshooter">
            <one @changeAnswer="updateActiveQuestions"/>
            <two @changeAnswer="updateActiveQuestions"/>
        </div>
    </div>
</template>

<script>
import one from './components/one.vue'
import two from './components/two.vue'

export default {
    name: 'app',
    components: {
        one,
        two
    },
    data: function() {
        return {
            activeQuestions: []
        }
    },
    methods: {
        updateActiveQuestions(event) {
            let index = this.activeQuestions.findIndex( ({ id }) => id === event.id );
            if ( index === -1 ) {
                this.activeQuestions.push(event);
            } else {
                this.activeQuestions[index] = event;
            }
        }
    }
}
</script>

两个.vue

<template>
    <div v-if="show">
        <h3>{{ question }}</h3>
        <div class="c-troubleshooter__section"> 
            <div class="c-troubleshooter__input">
                <input type="radio" id="question-2-a" name="question-2" value="ja" v-model="answer">
                <label for="question-2-a">Ja</label>
            </div>
            <div class="c-troubleshooter__input">
                <input type="radio" id="question-2-b" name="question-2" value="nej" v-model="answer">
                <label for="question-2-b">Nej</label>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    data: function() {
        return {
            id: 2,
            question: "Bla bla bla?",
            answer: undefined,
            requires: [
                {
                    id: 1,
                    answer: "Ja"
                }
            ]
        }
    },
    computed: {
        show: function() {
            // Check in parent to see if requirements are there, if so return true
            return true;
        }
    },
    watch: {
        answer: function() {
            this.$emit('changeAnswer', {
                id: this.id,
                question: this.question,
                answer: this.answer
            })
        }
    }
}
</script>

【问题讨论】:

  • 尝试将所有问题数据放入父级,并将其作为道具传递给子级。子组件可能可以制成一个通用问题组件,您可以将其用于所有问题。 requires 可以是一个函数。如果以后有时间我会写一个答案。
  • 是的,我意识到每个问题都有一个组件很奇怪,但我不确定我能做到多么模块化,因为每个问题的模板可能有点不同。例如,其中一些包含图像或自定义元素,而另一些则没有。也就是说,这些组件中的大多数基本上是相似的。

标签: javascript vue.js vuejs2 vue-component


【解决方案1】:

这是一个相当广泛的问题,但我会尝试提供一些有用的指导。

第一个data 应该用于internal 状态。很多时候,一个组件应该使用props 来表示你可能认为它拥有的data。这里就是这种情况:问题需要由父母协调,因此父母应该拥有数据。这使您可以创建一个合理的函数来控制问题组件是否显示。

让父级拥有数据还允许您制作一个问题组件,该组件根据其道具进行自我配置。或者您可能有几种不同的问题组件类型(您可以使用:is 来选择正确的一种),但如果您传递他们的问题/答案/其他信息,几乎可以肯定其中一些是可重复使用的。

要更新答案,您将从问题组件发出更改,并让父级实际更改值。我使用一个可设置的计算来允许在组件中使用v-model

new Vue({
  el: '#app',
  data() {
    return {
      questions: [{
          id: 1,
          question: 'blah 1?',
          answer: null
        },
        {
          id: 2,
          question: 'blah 2?',
          answer: null,
          // this is bound because data is a function 
          show: () => {
            const q1 = this.questions.find((q) => q.id === 1);

            return Boolean(q1.answer);
          }
        },
        {
          id: 3,
          question: 'Shows anyway?',
          answer: null
        }
      ]
    };
  },
  components: {
    questionComponent: {
      template: '#question-template',
      props: ['props'],
      computed: {
        answerProxy: {
          get() {
            return this.answer;
          },
          set(newValue) {
            this.$emit('change', newValue);
          }
        }
      }
    }
  }
});
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <div class="c-troubleshooter">
    <question-component v-for="q in questions" v-if="!q.show || q.show()" :props="q" @change="(v) => q.answer = v" :key="q.id">
    </question-component>
  </div>
  <h2>State</h2>
  <div v-for="q in questions" :key="q.id">
    {{q.question}} {{q.answer}}
  </div>
</div>

<template id="question-template">
  <div>
    {{props.question}}
    <div class="c-troubleshooter__input">
        <input type="radio" :id="`question-${props.id}-a`" :name="`question-${props.id}`" value="ja" v-model="answerProxy">
        <label :for="`question-${props.id}-a`">Ja</label>
    </div>
    <div class="c-troubleshooter__input">
        <input type="radio" :id="`question-${props.id}-b`" :name="`question-${props.id}`" value="nej" v-model="answerProxy">
        <label :for="`question-${props.id}-b`">Nej</label>
    </div>
  </div>
</template>

【讨论】:

  • 我意识到这是一个广泛的问题,但有时您遇到的问题并不是那么具体。非常感谢您的反馈,对您有很大帮助!
【解决方案2】:

有条件地呈现问题

正如@Roy J 在 cmets 中建议的那样,问题数据可能属于父级。处理所有数据并决定应该呈现哪些问题的是父母。但是,对此有很多策略:

  1. 直接在父模板中使用v-ifv-show 有条件地显示问题:

也许显示某些问题的逻辑根本不通用。它可能取决于更多的东西,用户设置......我不知道。如果是这种情况,只需有条件地直接在父级中呈现问题,因此您无需访问任何问题中的整个问题数据。代码应如下所示:

<template>
    <div id="app">
        <div class="c-troubleshooter">
            <one @changeAnswer="updateActiveQuestions" v-if="displayQuestion(1)"/>
            <two @changeAnswer="updateActiveQuestions" v-if="displayQuestion(2)"/>
        </div>
    </div>
</template>

<script>
import one from './components/one.vue'
import two from './components/two.vue'

export default {
    name: 'app',
    components: {
        one,
        two
    },
    data: function() {
        return {
            activeQuestions: [],
        }
    },
    methods: {
        updateActiveQuestions(event) {
            let index = this.activeQuestions.findIndex( ({ id }) => id === event.id );
            if ( index === -1 ) {
                this.activeQuestions.push(event);
            } else {
                this.activeQuestions[index] = event;
            }
        },
        displayQuestion(index){
          // logic...
        }
    },
}
</script>
  1. 将上一个问题的引用传递给每个问题:

如果任何问题只有在前一个问题已被回答或查看或类似的情况下才可见,您可以将其作为道具传递给每个问题,以便他们知道是否必须呈现:

<template>
    <div id="app">
        <div class="c-troubleshooter">
            <one @changeAnswer="updateActiveQuestions"/>
            <two @changeAnswer="updateActiveQuestions" prev="activeQuestions[0]"/>
        </div>
    </div>
</template>

two.vue:

props: ['prev'],
computed: {
    show: function() {
        return this.prev && this.prev.status === 'ANSWERED';
        // or some logic related to this, idk

    }
},
  1. 只需将整个数据传递给孩子:

在编写代码时,您可以将整个问题数据作为道具传递给每个问题组件,然后在计算属性中使用它。这不是我会做的,但它只是工作,因为对象是引用,所以这不一定是不合格的。

使用通用组件:

每个问题都有一个one.vuetwo.vue 似乎很奇怪,而且肯定不能很好地扩展。

我不确定我能做到多模块化,因为每个问题的模板可能会有所不同。例如,其中一些包含图像或自定义元素,而另一些则没有。

如果每个问题的模板真的不同,这可能会变得复杂。但是,如果我怀疑它们共享共同的 HTML 结构,在底部有一个定义的标题或一个共同的“询问”按钮以及类似的东西,那么你 should be able to address this using Vue slots

除了模板问题,我想你的应用程序中的每个问题都可以得到任意数量的“子问题”(如two.vue 具有question-2-aquestion-2-b)。这将需要问题数据的复杂而灵活的数据结构(当您开始添加多个选择、多个可能的答案等时,它会变得更加复杂)。这可能会变得非常复杂,但您可能应该继续努力,直到您可以使用单个 question.vue 组件,这肯定会得到回报。

提示:避开观察者

您在two.vue 模板中使用v-modelanswer,然后使用观察程序跟踪answer 变量的变化并发出事件。这令人费解且难以阅读,您可以在 &lt;input&gt; 元素上使用 @input@change 事件:

<input type="radio" id="question-2-a" name="question-2" value="ja" v-model="answer" @input="emitAnswer">

然后用方法代替观察者:

emitAnswer() {
this.$emit('changeAnswer', {
    id: this.id,
    question: this.question,
    answer: this.answer
})

【讨论】:

  • 非常感谢这些提示,非常有帮助!
猜你喜欢
  • 2019-06-09
  • 2021-08-15
  • 1970-01-01
  • 2018-10-18
  • 2020-05-16
  • 2021-03-07
  • 1970-01-01
  • 2019-12-23
  • 2019-04-11
相关资源
最近更新 更多