Vue-根据父数据的状态渲染组件

时间:2018-11-27 14:37:16

标签: javascript vue.js vuejs2 vue-component

我试图根据父对象(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>

two.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>

2 个答案:

答案 0 :(得分:1)

有条件地呈现问题

正如@Roy J在评论中建议的那样,问题数据可能属于父级。父母负责处理所有数据,并决定应提出哪些问题。但是,有很多策略可以做到这一点:

  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组件为止,这肯定会有所回报。

提示:避免观看者

您正在v-model模板中使用answertwo.vue,然后使用观察程序来跟踪answer变量中的更改并发出事件。这很复杂且难以理解,您可以在@input元素上使用@change<input>事件:

<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
})

答案 1 :(得分:0)

这是一个相当广泛的问题,但是我将尝试给出一些有用的指导。

第一个data应该用于内部状态。很多时候,一个组件应该使用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>