从 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 并且我有相同的行为,我需要回答:
我怎么能只跟踪 "model" 的几个属性(我相信这是可能的,因为性能优化会关注这样一个使用过的库)
我怎样才能在第一次初始化后才启动"watch"(并防止在开始时弹出"alert")
如您所见,我没有"one module for his DOM piece" 例如:changeTracker.hasChanges 用于 DOM[=15= 的顶部和底部]
如果我使用组件来管理 "model" 只有它自己的一块 DOM.. 我如何在里面使用 $parent ???
你问的是一个相当复杂的问题(或者我应该说的问题)。在基本层面上,这些答案应该足够了 - 以及提供的例子,它涵盖了你的许多问题。如果您有任何问题,请告诉我。
如果要跟踪某些属性,可以使用 computed
属性或 watch
属性。 Computed properties vs Watched properties
如果您想跳过 watch
属性 中的第一个更改,则必须集成某种逻辑,例如设置 bool
标志。
有几种不同的方法可以在组件中使用 "external" js 文件 - 您可以只导入它并使用必要的功能等。或者您可以使用 mixin
。 More on mixins here
子组件emit
数据给父组件,父组件通过props
传递数据给子组件。下面的示例显示了这一点。
编辑: 你问的是 mixins
以及如何将 "third" 派对模块添加到组件.. This CodePen does 产生的结果与我在下面提供的原始结果相同,只是它使用了 mixin
。这演示了如何通过 mixin
在任何组件中执行 "whatever you want" ..(或者我希望它至少这样做)...
示例代码:
/*****************************/
/* 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>
我想将我组织的应用程序从 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 并且我有相同的行为,我需要回答:
我怎么能只跟踪 "model" 的几个属性(我相信这是可能的,因为性能优化会关注这样一个使用过的库)
我怎样才能在第一次初始化后才启动"watch"(并防止在开始时弹出"alert")
如您所见,我没有"one module for his DOM piece" 例如:changeTracker.hasChanges 用于 DOM[=15= 的顶部和底部]
如果我使用组件来管理 "model" 只有它自己的一块 DOM.. 我如何在里面使用 $parent ???
你问的是一个相当复杂的问题(或者我应该说的问题)。在基本层面上,这些答案应该足够了 - 以及提供的例子,它涵盖了你的许多问题。如果您有任何问题,请告诉我。
如果要跟踪某些属性,可以使用 computed
属性或 watch
属性。 Computed properties vs Watched properties
如果您想跳过 watch
属性 中的第一个更改,则必须集成某种逻辑,例如设置 bool
标志。
有几种不同的方法可以在组件中使用 "external" js 文件 - 您可以只导入它并使用必要的功能等。或者您可以使用 mixin
。 More on mixins here
子组件emit
数据给父组件,父组件通过props
传递数据给子组件。下面的示例显示了这一点。
编辑: 你问的是 mixins
以及如何将 "third" 派对模块添加到组件.. This CodePen does 产生的结果与我在下面提供的原始结果相同,只是它使用了 mixin
。这演示了如何通过 mixin
在任何组件中执行 "whatever you want" ..(或者我希望它至少这样做)...
示例代码:
/*****************************/
/* 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>