使用 Vue.js 改写 React 的官方教程井字棋
React 的官方教程井字棋很好的引导初学者一步步走进 React 的世界,我想类似的教程对 Vue.js 的初学者应该也会有启发,于是使用 Vue.js 进行了改写
可以先查看最终的结果,尝试点击体验,我们将逐步地实现这个效果
初始状态代码
初始状态查看
打开初始状态直接编辑,或者将对应的文件复制下来放置在同一文件夹中
此时只是一个简单的井字棋格子,以及写死的下一个选手
初始代码分析
目前定义了三个组件,分别为 Square,Board 和 Game
Square 目前只是一个普通的按钮
Vue.component('Square', {
template: `
<button class="square">
{{ /* TODO */ }}
</button>
`
})
- 这样定义了组件后,别的组件就可以直接以 <Square /> 的方式引用该组件
Board 模版由当前状态和 9 个 Square 组成
Vue.component('Board', {
data() {
return {
status: `${nextLabel}X`,
board: [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]
}
},
template: `
<div>
<div class="status">{{ status }}</div>
<div class="board-row" v-for="(row, index) in board" :key="index">
<Square v-for="square in row" :key="square" />
</div>
</div>
`
});
- data 定义了当前状态 status,和 board 的值,这样在模版中就可以用 {{ status }} 的方式引用状态值,使用 v-for 将 board 二维数组里的值两次循环组装成井字格
- 在组件中的 data 必须是返回对象的函数而非对象字面值
- v-for 需要有 key 确保性能以及不报警告
Game 模版由 Board 与 后面需要增加的状态和历史组成
Vue.component('Game', {
template: `
<div class="game">
<div class="game-board">
<Board />
</div>
<div class="game-info">
<div>{{ /* status */ }}</div>
<ol>{{ /* TODO */ }}</ol>
</div>
</div>
`
});
增加数据处理
增加 Props
在 Board 中传递一个名为 value 的 prop 到 Square
<Square v-for="square in row" :key="square" :value="square" />
- :value 是 v-bind:value 的缩写,表示其值是一个表达式
在 Square 的组件定义和模版中增加 value prop
Vue.component('Square', {
props: ['value'],
template: `
<button class="square">
{{ value }}
</button>
`
})
- props 为父组件可传递给子组件的变量,在父组件调用子组件时在标签中设置对应属性,在子组件中使用方法与 data 一致
目前的代码和效果:0 - 8 的数字分别填充进井字棋格中
增加交互
增加点击事件至按钮元素以更新值
Vue.component('Square', {
//props: ['value'],
data() {
return {
value: null
}
},
methods: {
setValue() {
this.value = 'X';
}
},
template: `
<button class="square" @click="setValue">
{{ value }}
</button>
`
})
- @click 为 v-on:click 的缩写,其值为点击需要运行的函数,这里为组件定义的方法 methods 中的 setValue
- 子组件不能直接更新父组件的值,所以将 value 从 props 改为 data
- data 的值更新,对应模版就会自动更新展示内容
目前的代码和效果:点击井字棋格,对应填充 X
完善游戏
数值提升
为交替落子和确认输赢,需要统一判断各格状态,所以将 value 提升至 Board
Board 增加数据 squares 和方法 handleClick
Vue.component('Board', {
data() {
return {
...
squares: Array(9).fill(null),
}
},
methods: {
handleClick(i) {
const squares = this.squares.slice();
if (squares[i]){
alert('此位置已被占!');
return
}
squares[i] = 'X';
this.squares = squares;
}
},
template: `
...
<div class="board-row" v-for="(row, index) in board" :key="index">
<Square v-for="square in row" :key="square" :value="squares[square]" @click="handleClick(square)" />
- squares 初始为 9 个 null 组成的数组,井字棋盘为空的状态
- handleClick 接收对应格子序号的参数,并更新对应的 square 元素
- 事件处理器不是 handleClick(square) 的返回值,而是 handleClick,只是在触发时会带上参数值 square
在 Square 的点击事件处理器中触发 Board 的点击事件
Vue.component('Square', {
props: ['value'],
methods: {
setValue() {
this.$emit('click');
}
},
- value 要从 data 改回到 props
- $emit 可以调用父组件传递的事件处理器
- prop 里的值在父组件更新,子组件模版也会对应更新展示内容
目前的代码和效果:点击井字棋格,如果未被占,则填充 X
轮流落子
增加数据 xIsNext,并在点击时切换
data() {
return {
...
xIsNext: true
}
},
methods: {
handleClick(i) {
...
squares[i] = this.xIsNext ? 'X' : 'O';
this.squares = squares;
this.xIsNext = !this.xIsNext;
this.status = `${nextLabel}${this.xIsNext ? 'X' : 'O'}`;
- xIsNext 初始值为 true,即 X 先落子
- 点击后,通过取反交替 xIsNext
- 更新状态值 status 为下一个落子者
目前的代码和效果:点击井字棋格,X 和 O 交替落子
判断胜者
增加计算胜者的函数
function calculateWinner(squares) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
- 列举可能获胜的组合,与 squares 数组的值进行比对
增加点击处理函数的胜者逻辑
if (calculateWinner(squares)) {
alert('胜负已定!');
return;
}
...
const winner = calculateWinner(squares);
if (winner) {
this.status = '获胜者: ' + winner;
return;
}
- 点击后,如果之前已有取胜,则点击无效
- 处理落子后,再次判断是否取胜,更新状态
目前的代码和效果:有一方获胜后, 状态和点击处理更新
增加时间旅行
保存历史记录
为实现“悔棋”功能,需要记录每一次落子的整体状态,相当于棋盘的快照,作为一个历史记录,提升至 Game 组件中
在 Game 增加数据 history,将 xIsNext,status 和 handleClick 方法 从 Board 中转移到 Game 中
Vue.component('Game', {
data() {
return {
history: [{
squares: Array(9).fill(null),
}],
xIsNext: true,
status: `${nextLabel}X`
}
},
methods: {
handleClick(i) {
const history = this.history;
const current = history[history.length - 1]
const squares = current.squares.slice();
...
squares[i] = this.xIsNext ? 'X' : 'O';
history.push({
squares: squares
});
...
}
},
template: `
<div class="game">
<div class="game-board">
<Board :squares="history[history.length - 1].squares" @click="handleClick" />
`
})
- squares 从 history 的最后一个记录取值(目前只有一个记录)
- 落子后,squares 把落子记录进去后,history 再增加一个记录
Board 增加 prop squares,handleClick 更新为调用父组件的事件处理器
Vue.component('Board', {
props: ['squares'],
methods: {
handleClick(i) {
this.$emit('click', i);
}
},
目前的代码和效果:状态位置更新,历史记录已存储
展示历史步骤记录
把历史记录循环展示出来,并绑定点击事件,通过 stepNumber 的更新显示对应步骤的记录
Vue.component('Game', {
data() {
...
stepNumber: 0,
...
}
},
methods: {
handleClick(i) {
const history = this.history.slice(0, this.stepNumber + 1);
...
this.history = history.concat([{
squares: squares
}]);
this.stepNumber = history.length;
...
},
jumpTo(step) {
if(step === this.stepNumber){
alert('已在' + (0 === step ? '最开始' : `步骤#${step}!`));
return;
}
this.stepNumber = step;
this.xIsNext = (step % 2) === 0;
this.status = `${nextLabel}${this.xIsNext ? 'X' : 'O'}`;
}
},
template: `
<div class="game">
<div class="game-board">
<Board :squares="history[this.stepNumber].squares" @click="handleClick" />
</div>
<div class="game-info">
<div>{{ status }}</div>
<ol>
<li v-for="(squares, index) in history" :key="index" :class="{'move-on': index === stepNumber}">
<button @click="jumpTo(index)">{{ 0 === index ? '回到开始' : '回到步骤#' + index }}</button>
...
`
})
- 在 Game 中增加 stepNumber,初始为 0,记录当前展示的步骤
- 将 Board 的 prop squares 的取值更新为 this.stepNumber 对应的步骤
- handleClick 中以已当前步骤为基础处理 history,并更新 stepNumber
- 增加方法 jumpTo 处理回到历史的展示,更新 stepNumber,xIsNext 和 status
最终的代码和效果:每落一子,都会增加一个历史步骤,点击步骤可回到该步
总结
游戏实现内容
- 交替落子
- 判断输赢
- 悔棋重来
展示技术内容
- v-bind:在模版中进行数据绑定
- v-for:在模版中进行数组循环
- v-on, $emit:在组件间进行事件传递和触发
- data:在组件的定义和模版自动更新
- prop:在组件间的传递和模版自动更新
使用 Vue.js 改写 React 的官方教程井字棋的更多相关文章
- Vue.js与React的全面对比
Vue与React的对比 Vue.js与React.js从某些反面来说很相似,通过两个框架的学习,有时候对一些用法会有一点思考,为加深学习的思索,特翻阅了两个文档,从以下各方面进行了对比,加深了对这两 ...
- Vue.js vs React vs Angular 深度对比[转]
这个页面无疑是最难编写的,但我们认为它也是非常重要的.或许你曾遇到了一些问题并且已经用其他的框架解决了.你来这里的目的是看看 Vue 是否有更好的解决方案.这也是我们在此想要回答的. 客观来说,作为核 ...
- vue.js与react.js相比较的优势
vue.js的简介 vue.js是一个javascript mvvm库,它是以数据驱动和组件化的思想构建的.我们平时多用js去操作dom,vue.js则是使用了数据绑定驱动来操作dom的,也就是说创建 ...
- 【软件编程】乐易贵宾VIP教程 - JS改写+网页操作系列教程
JS改写系列教程: 1.MD5加密改写教程(爱拍网登录)2.解密如何快速找到真确的js加密算法3.多重MD5加密改写教程(5173登录)4.DZ论坛登录加密改写5.唯品会手机登录加密改写6.新浪微博密 ...
- Vue.js + Webpack + ECMAScript 6 入门教程
Vue.js学习教程 1.Vue.js——60分钟快速入门 2.Vue.js——60分钟组件快速入门(上篇) 3.Vue.js——60分钟组件快速入门(下篇) 4.Vue.js——基于$.ajax实现 ...
- 你是否有一个梦想?用JavaScript[vue.js、react.js......]开发一款自定义配置视频播放器
前言沉寂了一周了,打算把这几天的结果呈现给大家.这几天抽空就一直在搞一个自定义视频播放器,为什么会有如此想法?是因为之前看一些学习视频网站时,看到它们做的视频播放器非常Nice!于是,就打算抽空开发一 ...
- 我从Angular 2转向Vue.js, 也没有选择React
译者按: 通过使用Angular的经历,作者已经完全转为Vue粉了!我们Fundebug目前还是用AngularJS 1,坦白说,学习曲线蛮陡的. 原文: Why we moved from Angu ...
- 为什么我们从Angular 2迁移到Vue.js(为什么我们没有选择React)
在Rever(www.reverscore.com),我们刚刚使用Vue.js发布了我们的Web客户端的新版本.经过641次提交和16周的紧张开发,我们非常自豪之前做出的决定.8个月前,我们的前端在使 ...
- 公司内部技术分享之Vue.js和前端工程化
今天主要的核心话题是Vue.js和前端工程化.我将结合我这两年多的工作学习经历来谈谈这个,主要侧重点是前端工程化,Vue.js侧重点相对前端工程化,比重不是特别大. Vue.js Vue.js和Rea ...
随机推荐
- 360若真入股HTC 到底是谁来拯救谁
到底是谁来拯救谁" title="360若真入股HTC 到底是谁来拯救谁"> 我总是持有一种观点,那就是拯救是相互的.就像老师拯救"堕落"学生, ...
- Linux sed && awk
sed sed -i '/exit 0/i\ip route add 10.0.0.0/8 via '$gateway_ip'' /etc/sysconfig/network-scripts/ifup ...
- Docker实战之MySQL主从复制
前言 曾几何时,看着高大上的架构和各位前辈高超的炫技,有没有怦然心动,也想一窥究竟?每当面试的时候,拿着单应用的架构,吹着分库分表的牛X,有没有心里慌的一批? 其实很多时候,我们所缺少的只是对高大上的 ...
- 量化投资学习笔记29——《Python机器学习应用》课程笔记03
聚类的实际应用,图像分割. 利用图像的特征将图像分割为多个不相重叠的区域. 常用的方法有阈值分割,边缘分割,直方图法,特定理论(基于聚类,小波分析等). 实例:利用k-means聚类算法对图像像素点颜 ...
- 达拉草201771010105《面向对象程序设计(java)》第十八周学习总结
达拉草201771010105<面向对象程序设计(java)>第十八周学习总结 实验十八 总复习 实验时间 2018-12-30 1.实验目的与要求 (1) 综合掌握java基本程序结构 ...
- .NET Core C#目录
.NET Core技术开发指南 简介 本系列教程是一份关于微软.NET Core技术栈的全面的教程,其中涉及了C#.Typescript.Angular.Redis等一系列的教程.其中教程中通常会含有 ...
- JDK 1.8 新特性之Date-Time API
来源:请点击查看 1.8之前的日期类: 线程不安全:java.util.Date 这个类线程不安全,而且所有日期类都是可变的. 时间处理麻烦:默认的开始日期从1900年,不支持国际化,不提供时区支持, ...
- 《数字信号处理》课程实验1 – FFT的实现
一.按时间抽选的基-2 FFT实现原理 观察DIT(基2)FFT的流图(N点,N为2的幂次),可以总结出如下规律: (1)共有\(L=\log_2N\)级蝶形运算: (2)输入倒位序,输出自然顺序: ...
- 前端每日实战:111# 视频演示如何用纯 CSS 创作一只艺术的鸭子
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/aaoveW 可交互视频 此视频是可 ...
- JDBC概述及编程步骤详解
目录 一.JDBC概述 二.JDBC编程步骤(以MySQL为例) 1.导入jar包 2.加载数据库驱动 3.通过DriverManager获得Connection对象 3.定义SQL语句 4.利用Co ...