从 Knockout 切换到 VueJs:缺少概念

Switching from Knockout to VueJs : missing concept

我想将我组织的应用程序从 Knockout 切换到 Vue,但缺少一些概念或者我误解了 vueJs 的理念。所以我无法重现相同的行为。

据我所知,VueJs 确实专注于组件。

每个组件管理都是自己的一块DOM。

https://fr.vuejs.org/v2/guide/reactivity.html

https://fr.vuejs.org/v2/guide/list.html

...(我在开始输入键盘之前阅读了本指南的大部分内容 :p)

这种行为对于需要共享的简单组件来说很好,但在我的情况下它有点令人沮丧,因为我需要链接一些模块(请参阅最后关于上下文的问题)

我读过另一篇关于 "knockout to Vuejs transition" 的精彩文章 (https://jes.al/2017/05/migrating-from-knockoutjs-to-vuejs/),但它缺少如此具体的细节。

例如:它解释了 "subscribe"(在 KO 中)可以替换为 "watch"(在 Vue 中)但没有解释 "when" 我们可以应用这些观察者(参见我的问题在最后)

这是一个非常小的代码提取,可以概括我的大部分特定需求。

Html

<div id="app">
 <div data-bind="text: title"></div>
 <i data-bind="visible: changeTracker.hasChanges">save your change dude !!</i>
 <div data-bind="with: model">
     <!-- the binging  "with" allow us to move context to model for the current node and prevent typing "model." each time (ex: model.prop1, modeld.pro2,) -->

     <label data-bind="text: name"></label>
     <input type="text" data-bind="value: firstName"/>

     <span data-bind="text: $parent.nameAndFirstName">
     <!-- "$parent" allow us to access vm context itself -->

     <input type="text" data-bind="value: city"/>
 </div> 
 <div>
    <button data-bind="click: changeTracker.undo, enabled: changeTracker.hasChanges"></button>
    <button data-bind="click: saveData, enabled: changeTracker.hasChanges"></button>
 </div>
</div>

Javascript

var changeTrackerVM_Factory = function(){
    var self = {};

    self.initialState = null; // no need to be ko.observable since it's used for internal manipulation only

    // flag that will tell the UI that modification exist
    self.hasChanges = ko.observable(false);

    self.init = function(modelToTrack){
        // some piece of code .... subscribe ... and so on
    }

    self.undo = function(){
         // some piece of code that revrt the object to it's initial state
    };

    return self;
};

var userVM_Factory = function(){
   var self = {};
   // data from-to database
   self.model = {
       id: "", //-- don't need to be observable since it's not rendered and not modifiable
       name: ko.observable(""),
       firstName: ko.observable(""),
       city: ko.observable("")
   };

   self.nameAndFirstName = ko.computed(function(){
     return self.model.name() + "-" + self.model.firstname();
   });

   // build the custom tracker module
   self.changeTracker = changeTrackerVM_Factory(); 

   self.init = function(){
       //some ajaxRequest
       $.ajax(....).then(function(data){
            // map recieved datas
            self.model.name(data.name);
            ......

            // apply custom observation AFTER data mapping
            self.model.city.subscribe(function(newvalue){
                 alert("yeaah !! YOU changed the city value");
            });

            // apply the "custom tracking module" used to show the state in the UI
            self.changeTracker.init(self.model);
       });
   }

   self.save = function(){
        // some piece of code to send to DATABASE
   }

   return self;
};

// initialise the relation between the UI and the VM
var userVM = userVM_Factory();
ko.applybinding(userVM , "#app");

如果我想将此代码转换为 vueJS 并且我有相同的行为,我需要回答:

你问的是一个相当复杂的问题(或者我应该说的问题)。在基本层面上,这些答案应该足够了 - 以及提供的例子,它涵盖了你的许多问题。如果您有任何问题,请告诉我。

如果要跟踪某些属性,可以使用 computed 属性或 watch 属性。 Computed properties vs Watched properties

如果您想跳过 watch 属性 中的第一个更改,则必须集成某种逻辑,例如设置 bool 标志。

有几种不同的方法可以在组件中使用 "external" js 文件 - 您可以只导入它并使用必要的功能等。或者您可以使用 mixinMore on mixins here

子组件emit数据给父组件,父组件通过props传递数据给子组件。下面的示例显示了这一点。


编辑: 你问的是 mixins 以及如何将 "third" 派对模块添加到组件.. This CodePen does 产生的结果与我在下面提供的原始结果相同,只是它使用了 mixin。这演示了如何通过 mixin 在任何组件中执行 "whatever you want" ..(或者我希望它至少这样做)...


示例代码:

[CodePen mirror]

[CodePen using mixin]


/*****************************/
/* User VM Factory Component */
/*****************************/
const userVmFactoryComponent = {
  name: "userVmFactoryComponent",
  template: "#userVmFactoryComponent",
  props: {
    id: [String, Number],
    name: String,
    lastName: String,
    city: String,
  },
  data() {
    return {
      user: {
        id: "",
        name: "",
        lastName: "",
        city: "",
      }
    }
  },
  methods: {
    emitNameChanged(){
      this.$emit('name-changed', this.fullName);
    }
  },
  computed: {
    fullName() { // using fullName vs nameAndFirstName
      return this.user.name + " " + this.user.lastName;
    }
  },
  mounted(){
    this.user.id = this.id;
    this.user.name = this.name;
    this.user.lastName = this.lastName;
    this.user.city = this.city;
  }
}

/****************/
/* Main Vue App */
/****************/
new Vue({
  el: "#app",
  components: {
    userVmFactoryComponent,
  },
  data: {
    users: [],
  },
  methods: {
    handleNameChange(info) {
      alert("You changed the name! (alerted from parent) " + "'" + info + "'")
    }
  },
  mounted() {
    // pretend you're getting this data from an API
    let firstUser = {
      id: 100,
      name: 'John',
      lastName: 'Smith',
      city: 'Paris',
    };
    
    let secondUser = {
      id: 200,
      name: "Jane",
      lastName: "Doe",
      city: "New York",
    }
    
    this.users.push(firstUser);
    this.users.push(secondUser);
  }
})
.user-input {
  margin-bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>


<!-- MAIN VUE APP -->
<div id="app">
  <div>
  <div>
    <div>Without Looping</div>
    <user-vm-factory-component 
      @name-changed="handleNameChange"
      :id="users[0].id" 
      :name="users[0].name" 
      :last-name="users[0].lastName" 
      :city="users[0].city"
    ></user-vm-factory-component>
    <br/>
    <user-vm-factory-component 
      @name-changed="handleNameChange"
      :id="users[1].id" 
      :name="users[1].name" 
      :last-name="users[1].lastName" 
      :city="users[1].city"
    ></user-vm-factory-component>
  </div>
    <br/><hr/><br/>
    <div>
      <div>With Looping</div>
      <div>
        <div v-for="(user, index) in users">
          <user-vm-factory-component
            @name-changed="handleNameChange"
            :id="user.id"
            :name="user.name"
            :last-name="user.lastName"
            :city="user.city"
          ></user-vm-factory-component>
          <br/>
        </div>
      </div>
    </div>
  </div>
</div>

<!-- ===================================== -->
<!-- THIS SIMULATES userVmFactoryComponent -->
<!-- ===================================== -->
<script type="text/x-template" id="userVmFactoryComponent">
  <div>
    Id
    <div class="user-input">
      <input type="text" v-model="user.id" />
    </div>
    Name (alerts when changed)
    <div class="user-input">
      <input type="text" @input="emitNameChanged" v-model="user.name" />
    </div>
    LastName
    <div class="user-input">
      <input type="text" v-model="user.lastName" />
    </div>
    FullName
    <div class="user-input">
      <input type="text" v-model="fullName" />
    </div>
    City
    <div class="user-input">
      <input type="text" v-model="user.city" />
    </div>
  </div>
</script>