上一次学习 web 开发还是在上一次。
经过了三天的前端学习后,我终于准备开始学习 FullStack 了。
仔细想了想,还是不要待在 comfort zone 里,干脆直接学 js 全栈,也可以提高 js 的熟练度。
衔接上一次的 Day3.
小学数学训练的项目暂且搁置了,等我神功小成再回来重构吧……
这几天可能学习一下 vue 和 tailwind css(或者 bootstrap?)
Position
relative,相对于原本位置的 position。通过定义 left/right/top/bottom 来确定相对位置。注意这里定义的是偏移量,即相对于其原本应在的位置的偏移。 absolute,绝对位置,相对于 parent 的 position,通过定义 left/right/top/bottom 来确定其位置。 note: 这里的 parent 是祖先中第一个 position 为 relative 或 absolute 的,因此使用时一般设置 parent container 的 position 为 relative. fixed,固定于屏幕上某个位置,不会随着翻页而改变。 sticky,经常在导航栏上使用的东西。一个基础的 Form:
<form action="http://httpbin.org/post" method="post">
<div>
<label for="first_name">First Name:</label>
<input type="text" name="first_name" id="first_name">
</div>
<div>
<label for="last_name">Last Name:</label>
<input type="text" name="last_name" id="last_name">
</div>
<div>
<label for="age">Age:</label>
<input type="number" name="age" id="age">
</div>
<button type="submit">Submit</button>
</form>
name 是传参的名字,id 跟其他 html element 中是一个意义的。
<textarea></textarea>
<textarea rows="20" cols="60"></textarea>
textarea 是可缩放的多行编辑框,用 rows 和 cols 来定义初始大小。
<select name="Car">
<option value="mercedes">Mercedes</option>
<option value="tesla">Tesla</option>
<option value="volvo">Volvo</option>
<option value="bmw">BMW</option>
<option value="mini">Mini</option>
<option value="ford">Ford</option>
</select>
select 是下拉选项,value 是传参时的值。
<option value="volvo" selected>Volvo</option>
可以设置默认选项。
可以分组。
<select name="fashion">
<optgroup label="Clothing">
<option value="t_shirt">T-Shirts</option>
<option value="sweater">Sweaters</option>
<option value="coats">Coats</option>
</optgroup>
<optgroup label="Foot Wear">
<option value="sneakers">Sneakers</option>
<option value="boots">Boots</option>
<option value="sandals">Sandals</option>
</optgroup>
</select>
Radio Button 是列表单选项,通过 name 来判定为同一组。同组中只能选一个。
<h1>Ticket Type</h1>
<div>
<input type="radio" id="child" name="ticket_type" value="child">
<label for="child">Child</label>
</div>
<div>
<input type="radio" id="adult" name="ticket_type" value="adult">
<label for="adult">Adult</label>
</div>
<div>
<input type="radio" id="senior" name="ticket_type" value="senior">
<label for="senior">Senior</label>
</div>
用 checked 来默认选中。
<input type="radio" id="adult" name="ticket_type" value="adult" checked>
Checkbox,多选。
<div>
<input type="checkbox" id="sausage" name="topping" value="sausage">
<label for="sausage">Sausage</label>
</div>
<div>
<input type="checkbox" id="onions" name="topping" value="onions">
<label for="onions">Onions</label>
</div>
<div>
<input type="checkbox" id="pepperoni" name="topping" value="pepperoni">
<label for="pepperoni">Pepperoni</label>
</div>
<div>
<input type="checkbox" id="mushrooms" name="topping" value="mushrooms">
<label for="mushrooms">Mushrooms</label>
</div>
Button 就更常用了。可以在 form 中定义 submit 类型的 button,一键提交。 还有 reset,回归默认。 如果想要朴素的 button,那得手动设定 type.
form 中 button 的 type 默认是 submit,这个需要注意。
1 2 3 | <button type="submit">Submit</button> <button type="reset">Reset</button> <button type="button">Click to Toggle</button> |
---|
fieldset 可以给 field 分组。
1 2 3 4 5 6 7 | <fieldset> <label for="first_name">First Name</label> <input type="text" id="first_name" name="first_name"> <label for="last_name">Last Name</label> <input type="text" id="last_name" name="last_name"> </fieldset> |
---|
在 fieldset 后面添加 legend 可以加一个小标题。
1 2 3 | <fieldset> <legend>Contact Details</legend> ... |
---|
给 form control 添加 required attribute,表示必填。
1 2 3 4 | <div> <label for="user_email">*Email:</label> <input type="email" id="user_email" name="user_email" required> </div> |
---|
可以给 input 设置 minlength maxlength,给 type 为 number 的 input 设置 min/max 上下界,可以设置 pattern attribute 检验输入是否符合某个正则串。
过了 validation 的 control 会有一个 valid state,没过的对应 invalid.
1 2 3 4 5 6 7 | input:invalid { border-color: red; } input:valid { border-color: green; } |
---|
在前端检验完了,后端也依然要继续检验数据合法性,不然被抓包塞脏数据就不好了(
乍一看,还挺好看的……这一节的 Assignment 就是仿制一个这东西。
横向划分为两个部分,右侧划分为三个部分。
那个鬼畜的 ODIN logo 用 position 解决。
字体懒得设置了,看起来差不太多就行。
总感觉有点不对劲、、、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Sign Up</title> <style> body { display: flex; height: 100vh; margin: 0; padding: 0; } div#left { position: relative; height: 100%; width: 35vw; background-color: black; background-image: url("https://images.unsplash.com/photo-1585202900225-6d3ac20a6962?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80"); background-size: cover; background-position: center; } div#logo { position: absolute; top: 22%; background-color: rgba(0, 0, 0, 0.6); height: 15%; width: 100%; display: flex; justify-content: center; align-items: center; } div#logo div { font-family: 'Times New Roman', Times, serif; opacity: 80%; font-size: 15vh; color: aliceblue; } div#right { flex: 1 1 auto; background-color: WhiteSmoke; display: flex; flex-direction: column; justify-content: center; } div#right>#one { display: flex; align-items: flex-end; height: 25vh; position: relative; } div#right>#one>#intro { position: absolute; left: 2vw; bottom: 3vh; font-size: 1.2vw; font-weight: 500; width: 80%; } div#right>#two { height: 40vh; background-color: white; border: 1px solid antiquewhite; box-shadow: rgba(0, 0, 0, 0.35) 0px 5px 15px; padding: 2vh 2vw; } .main { width: 50vw; display: grid; grid-template-columns: auto auto; grid-template-rows: auto auto auto auto; column-gap: 2vw; } h2 { font-size: 2vw; } label { font-size: 1.2vw; color: rgb(50,50,50); } input { height: 2.5vh; border: 1px solid rgb(240,240,240); box-shadow: rgba(0, 0, 0, 0.16) 0px 1px 2px, rgba(0, 0, 0, 0.23) 0px 1px 2px; font-size: 1vw; } input:focus{ outline: 3px dashed skyblue; box-shadow: none; } .field { display: flex; flex-direction: column; width: 16vw; height: 8vh; } div#right>#three { height: 25vh; padding: 3vh 2vw; } button { width: 14vw; height: 6vh; background-color:darkolivegreen; box-shadow: rgba(14, 30, 37, 0.12) 0px 2px 4px 0px, rgba(14, 30, 37, 0.32) 0px 2px 16px 0px; border: 0; border-radius: 0.5vw; color:ivory; font-size: 1.5vw; } div#right>#three>p { font-size: 1.1vw; margin-top: 3vh; } div#right>#three>p>a { color:darkolivegreen; font-weight:700; } </style> </head> <body> <div id="left"> <div id="logo"> <div>ODIN</div> </div> </div> <div id="right"> <div id="one"> <div id="intro"> <p>This is not a real online service! You know you need something like this in your life to help you realize your deepest dreams. <br> Sign up <em> now </em> to get started.</p> <p>You <em> know </em>you want to.</p> </div> </div> <div id="two"> <h2>Let's do this!</h2> <form class="main" id="myForm"> <div class="field"> <label for="firstName">FIRST NAME</label> <input type="text" name="firstName" required></input> </div> <div class="field"> <label for="lastName">LAST NAME</label> <input type="text" name="lastName" required></input> </div> <div class="field"> <label for="email">EMAIL</label> <input type="email" name="email" required></input> </div> <div class="field"> <label for="phone">PHONE NUMBER</label> <input type="number" name="phoneNumber" required></input> </div> <div class="field"> <label for="password">PASSWORD</label> <input type="password" name="password" required></input> </div> <div class="field"> <label for="password">CONFIRM PASSWORD</label> <input type="password" name="password" required></input> </div> </form> </div> <div id="three"> <button form="myForm" type="submit">Create Account</button> <p>Already have an account? <a>Log in</a></p> </div> </div> </body> </html> |
---|
还有一个 Grid 相关的 project,既然我已经做过了 numpad,那就不做这个了……
Intermediate HTML and CSS 这个 Course 就到此结束了。
在 js 中,property 可以用两种方式访问:
1 2 | obj.property obj["property"] |
---|
后者的好处是可以处理带空格的 property name(不会真的有人起这样的名字吧、、)
object constructor 可以这么写:
1 2 3 4 5 6 7 8 9 10 11 12 | function Player(name, marker) { this.name = name this.marker = marker this.sayName = function() { console.log(name) } } const player1 = new Player('steve', 'X') const player2 = new Player('also steve', 'O') player1.sayName() // logs 'steve' player2.sayName() // logs 'also steve' |
---|
后面又引入了一个 prototype 的概念。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function Student() { } Student.prototype.sayName = function() { console.log(this.name) } function EighthGrader(name) { this.name = name this.grade = 8 } EighthGrader.prototype = Object.create(Student.prototype) const carl = new EighthGrader("carl") carl.sayName() // console.logs "carl" carl.grade // 8 |
---|
应该说所有的 instance 会共用一个 prototype. 那么在 prototype 上挂载函数,就可以避免每次 new instance 的时候现场创建,降低一些开销。 此外,还可以在 prototype 上挂些变量,读的时候会读到 prototype 上,但写了之后就在当前 obj 上声明了新变量,从而覆写 prototype 上的变量。
在寻找 property 时,obj 与其 prototype 及其各路祖先构成了一条 prototype chain,从这个链一路向上查找对应的 property. 跟继承有些类似。
似乎有建议说尽量写 factory function 而不要用 constructor 生成 obj.
1 2 3 4 5 6 7 8 9 10 | const personFactory = (name, age) => { const sayHello = () => console.log('hello!'); return { name, age, sayHello }; }; const jeff = personFactory('jeff', 27); console.log(jeff.name); // 'jeff' jeff.sayHello(); // calls the function and logs 'hello!' |
---|
在 factory function 中可以写出类似继承的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | const Person = (name) => { const sayName = () => console.log(`my name is ${name}`); return {sayName}; } const Nerd = (name) => { // simply create a person and pull out the sayName function with destructuring assignment syntax! const {sayName} = Person(name); const doSomethingNerdy = () => console.log('nerd stuff'); return {sayName, doSomethingNerdy}; } const jeff = Nerd('jeff'); jeff.sayName(); //my name is jeff jeff.doSomethingNerdy(); // nerd stuff |
---|
也可以靠 prototype 直接继承。
1 2 3 4 5 | const Nerd = (name) => { const prototype = Person(name); const doSomethingNerdy = () => console.log('nerd stuff'); return Object.assign({}, prototype, {doSomethingNerdy}); } |
---|
getter 和 setter:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | let user = { name: "John", surname: "Smith", get fullName() { return `${this.name} ${this.surname}`; }, set fullName(value) { [this.name, this.surname] = value.split(" "); } }; // set fullName is executed with the given value. user.fullName = "Alice Cooper"; alert(user.name); // Alice alert(user.surname); // Cooper |
---|
也可以用 defineProperty 来定义 accesssor.
1 2 3 4 5 6 7 8 9 | Object.defineProperty(user, 'fullName', { get() { return `${this.name} ${this.surname}`; }, set(value) { [this.name, this.surname] = value.split(" "); } }); |
---|
有的时候,class method 会出现 losing this 的情况。这往往是在将 method 作为 function 传参后出现的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Button { constructor(value) { this.value = value; } click() { alert(this.value); } } let button = new Button("hello"); setTimeout(button.click, 1000); // undefined |
---|
解决方案是套一层皮。
1 | setTimeout(() => button.click(), 1000) |
---|
可以直接 bind function
1 2 3 4 5 6 7 8 9 10 | let user = { firstName: "John" }; function func() { alert(this.firstName); } let funcUser = func.bind(user); funcUser(); // John |
---|
还有一个 class specific 的优雅方法,在 new obj 的时候新建一个 closure.
1 2 3 4 5 6 7 8 | class Button { constructor(value) { this.value = value; } click = () => { alert(this.value); } } |
---|
1 2 3 4 5 6 7 8 9 | class User { ['say' + 'Hi']() { alert("Hello"); } } new User().sayHi(); |
---|
用 extends 来继承,只能拥有唯一的 parent.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Animal { constructor(name) { this.name = name; } speak() { console.log(`${this.name} makes a noise.`); } } class Dog extends Animal { constructor(name) { super(name); // call the super class constructor and pass in the name parameter } speak() { console.log(`${this.name} barks.`); } } const d = new Dog('Mitzie'); d.speak(); // Mitzie barks. |
---|
可以用一些奇技淫巧来实现类似于继承多个 interface 的功能。
1 2 3 4 5 6 7 8 9 | const calculatorMixin = (Base) => class extends Base { calc() { } }; const randomizerMixin = (Base) => class extends Base { randomize() { } }; class Foo { } class Bar extends calculatorMixin(randomizerMixin(Foo)) { } |
---|
异步。js 和 backend 的交互需要用到。
一个比较传统的异步写法是利用 callback,比如执行完 IO 操作后执行传入的函数来传递结果。
1 2 3 4 5 6 7 8 9 10 | function myDisplayer(some) { document.getElementById("demo").innerHTML = some; } function myCalculator(num1, num2, myCallback) { let sum = num1 + num2; myCallback(sum); } myCalculator(5, 5, myDisplayer); |
---|
setTimeout 可以在超时时执行对应的 callback,setInterval 可以定期执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | setTimeout(myFunction, 3000); function myFunction() { document.getElementById("demo").innerHTML = "I love You !!"; } setInterval(myFunction, 1000); function myFunction() { let d = new Date(); document.getElementById("demo").innerHTML= d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); } |
---|
举一个 irl 的例子,File IO:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function myDisplayer(some) { document.getElementById("demo").innerHTML = some; } function getFile(myCallback) { let req = new XMLHttpRequest(); req.open('GET', "mycar.html"); req.onload = function() { if (req.status == 200) { myCallback(this.responseText); } else { myCallback("Error: " + req.status); } } req.send(); } getFile(myDisplayer); |
---|
这里利用了 eventListener,设置 onload event 的 callback.
接下来就学习调用 Web API 了。
当今时代,应当使用 fetch.
1 2 3 4 5 6 7 8 | // URL (required), options (optional) fetch('https://url.com/some/url') .then(function(response) { // Successful response :) }) .catch(function(err) { // Error :( }); |
---|
默认情况下,浏览器不允许本站对其他站的 http 请求。设置 Cross Origin Resource Sharing (CORS) 的模式就可以解决这个问题。
1 2 3 | fetch('url.url.com/api', { mode: 'cors' }); |
---|
调用 API 写一个天气预报的页面。
刚刚突然下雨把晒的被子淋湿了,有点应景、、、
首先找一个 API,这里选用 openweathermap.
申请了一个 API key,用 get 调用 API 就可以了,感觉还比较简单。
具体的显示懒得搞了,就这样吧。。。
昨天划水过多,进度严重拖慢了,糟糕。
v-if v-else-if v-else
1 2 3 4 5 6 7 8 9 10 11 12 | <div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div> |
---|
可以在 template 上直接打 v-if.
1 2 3 4 5 | <template v-if="ok"> <h1>Title</h1> <p>Paragraph 1</p> <p>Paragraph 2</p> </template> |
---|
还有一个 v-show.
1 | <h1 v-show="ok">Hello!</h1> |
---|
不同于 v-if,v-show 仅仅只切换了 element 的 display attr.
v-for 可以用于显示数组。
1 2 3 4 5 6 7 8 9 10 | <script> data() { return { items: [{ message: 'Foo' }, { message: 'Bar' }] } } </script> <li v-for="item in items"> {{ item.message }} </li> |
---|
遍历时也可以带上下标。
1 2 3 | <li v-for="(item, index) in items"> {{ parentMessage }} - {{ index }} - {{ item.message }} </li> |
---|
可以嵌套,也可以用 for of.
1 | <div v-for="item of items"></div> |
---|
obj 也可以 for,遍历 key value.
1 2 3 | <li v-for="(value, key) in myObject"> {{ key }}: {{ value }} </li> |
---|
也可以加上 index.
1 2 3 | <li v-for="(value, key, index) in myObject"> {{ index }}. {{ key }}: {{ value }} </li> |
---|
v-for 可以用在 template 上。
1 2 3 4 5 6 | <ul> <template v-for="item in items"> <li>{{ item.msg }}</li> <li class="divider" role="presentation"></li> </template> </ul> |
---|
v-for 是保留顺序的,在更改 reactive state 之后,可以动态调整顺序。但需要手动指定一个 key.
1 2 3 | <div v-for="item in items" :key="item.id"> <!-- content --> </div> |
---|
v-on 可以用 $event 来传递 event obj.
1 2 3 4 5 6 7 8 9 | <!-- using $event special variable --> <button @click="warn('Form cannot be submitted yet.', $event)"> Submit </button> <!-- using inline arrow function --> <button @click="(event) => warn('Form cannot be submitted yet.', event)"> Submit </button> |
---|
可以添加一些 modifier.
1 2 3 4 5 6 7 8 9 10 11 | <!-- use capture mode when adding the event listener --> <!-- i.e. an event targeting an inner element is handled here before being handled by that element --> <div @click.capture="doThis">...</div> <!-- the click event will be triggered at most once --> <a @click.once="doThis"></a> <!-- the scroll event's default behavior (scrolling) will happen --> <!-- immediately, instead of waiting for `onScroll` to complete --> <!-- in case it contains `event.preventDefault()` --> <div @scroll.passive="onScroll">...</div> |
---|
key 相关的事件还可以按 keycode 过滤。
1 2 3 4 | <!-- only call `vm.submit()` when the `key` is `Enter` --> <input @keyup.enter="submit" /> <input @keyup.alt.enter="clear" /> |
---|
.exact modifier 可以指定当且仅当某些键被按下。
1 2 | <!-- this will only fire when Ctrl and no other keys are pressed --> <button @click.ctrl.exact="onCtrlClick">A</button> |
---|
有的时候,我们需要将某个可输入 element 的值与我们的某个变量建立同步关系。
1 2 3 | <input :value="text" @input="event => text = event.target.value"> |
---|
提供了 v-model 这样的 syntax sugar.
1 | <input v-model="text"> |
---|
使用 v-model 时,默认值要在 data 中设置,在 html 中设置默认值会被覆盖。
checkbox 可以绑定数组。
1 2 3 4 5 6 7 8 9 10 | <div>Checked names: {{ checkedNames }}</div> <input type="checkbox" id="jack" value="Jack" v-model="checkedNames"> <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="checkedNames"> <label for="john">John</label> <input type="checkbox" id="mike" value="Mike" v-model="checkedNames"> <label for="mike">Mike</label> |
---|
默认情况下,v-model 在每次 input 事件后更新,用 lazy modifier 可以让其在 change 后更新,优化一点性能。
1 2 | <!-- synced after "change" instead of "input" --> <input v-model.lazy="msg" /> |
---|
lifecycle hooks 可以在特定时刻执行相应的操作。
1 2 3 4 5 | export default { mounted() { console.log(`the component is now mounted.`) } } |
---|
watch 可以监视某个 reactive state 是否改变,如果改变则执行代码。
export default {
data() {
return {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
}
},
watch: {
// whenever question changes, this function will run
question(newQuestion, oldQuestion) {
if (newQuestion.includes('?')) {
this.getAnswer()
}
}
},
methods: {
async getAnswer() {
this.answer = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
this.answer = (await res.json()).answer
} catch (error) {
this.answer = 'Error! Could not reach the API. ' + error
}
}
}
}
watch 默认是 shallow 的,有个 deep watcher. 注意这个可能有性能风险。
export default {
watch: {
someObject: {
handler(newValue, oldValue) {
// Note: `newValue` will be equal to `oldValue` here
// on nested mutations as long as the object itself
// hasn't been replaced.
},
deep: true
}
}
}
eager watcher 可以在 created 时也执行一次。
export default {
// ...
watch: {
question: {
handler(newQuestion) {
// this will be run immediately on component creation.
},
// force eager callback execution
immediate: true
}
}
// ...
}
默认情况下,state 变更后,会先触发 watch,再进行 component update,此时如果在 watcher 中访问 DOM,得到的是未变更的数据。
解决方案:
export default {
// ...
watch: {
key: {
handler() {},
flush: 'post'
}
}
}
可以这样创建 watcher:
export default {
created() {
this.$watch('question', (newQuestion) => {
// ...
})
}
}
可以手动停止 watcher,虽然很少用。在创建 watcher 时把对象保存下来,call 它就会停止。
const unwatch = this.$watch('foo', callback)
// ...when the watcher is no longer needed:
unwatch()
尽管用了 Vue 之后大部分时候都不需要直接访问 DOM,Vue 还是有着相关的功能。
用 ref 来指定 element,类似于 ID.
必须在 mounted 之后才能用 $refs 来访问元素。
<script>
export default {
mounted() {
this.$refs.input.focus()
}
}
</script>
<template>
<input ref="input" />
</template>
v-for 和 ref 搭配时,会生成一个数组,但不保证原序。
<script>
export default {
data() {
return {
list: [
/* ... */
]
}
},
mounted() {
console.log(this.$refs.items)
}
}
</script>
<template>
<ul>
<li v-for="item in list" ref="items">
{{ item }}
</li>
</ul>
</template>
ref 还可以指向一个 function,元素本身的 reference 会成为参数。
1 | <input :ref="(el) => { /* assign el to a property or ref */ }"> |
---|
ref 可以用在 component 上,得到对应的 component obj. 尽量避免这种用法,因为它会在 parent 与 children 之间建立强耦合。
vue 中的 component 一般使用 Single File Component(SFC) 定义,以 .vue 为文件名后缀。
使用 component 一般需要 import + register.
<script>
import ButtonCounter from './ButtonCounter.vue'
export default {
components: {
ButtonCounter
}
}
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
SFC 建议使用 PascalCase 命名。
Component 之间的信息交互使用 prop.
<!-- BlogPost.vue -->
<script>
export default {
props: ['title']
}
</script>
<template>
<h4>{{ title }}</h4>
</template>
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
定义好的 prop 变成了 element attr.
而 component 可以自己定义事件,用 $emit.
<!-- BlogPost.vue, omitting <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
emits 不强制显性定义。
<!-- BlogPost.vue -->
<script>
export default {
props: ['title'],
emits: ['enlarge-text']
}
</script>
slot 则这么用:
<AlertBox>
Something bad happened.
</AlertBox>
<!-- AlertBox.vue -->
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
可以用 component :is 来动态切换 component.
<!-- Component changes when currentTab changes -->
<component :is="currentTab"></component>
value 可以是 component name 或者是 component obj.
默认情况下是直接创建新的 component,不会保存状态。为此可以使用 KeepAlive 的 built-in component.
<!-- Inactive components will be cached! -->
<KeepAlive>
<component :is="activeComponent" />
</KeepAlive>
使用 Vue 重构我们可爱的小学数学计算训练器。
Vue 的 Component 可以做到模块化,有点 OOP 的味道。
写起来还挺方便的,用 template 写动态页面也比较舒服。适配 Mobile 也可以在同一个 component 上用 v-if 比较方便地完成。
像是从刀耕火种变成了机器农业hhh
下一步应该是学一个 css Framework,让界面更加好看一些。
然后再速成个 backend,我的 web dev 玩票入门就完成了。
可惜最近要开学了,时间不足,不然可以来个 7Days Into Web Development. :)