看到一位人士说,未来软件工程师只有两个方向:端工程师、云工程师。公司部门内,也有全栈工程师。前端的技术发展也非常快,很多思想跟后端已经是相通了。
下面以一个react示例,来稍作了解,这个示例是一个中文网的示例,但是我对代码进行了大量的注释,对一些变更进行了重命名,感觉会比较好理解一点。
-
安装node.js,具体前期操作看上述链接。
-
添加css文件,内容如下:
body {
font: 14px "Century Gothic", Futura, sans-serif;
margin: 20px;
}
ol, ul {
padding-left: 30px;
}
.board-row:after {
clear: both;
content: "";
display: table;
}
.status {
margin-bottom: 10px;
}
.square {
background: #fff;
border: 1px solid #999;
float: left;
font-size: 24px;
font-weight: bold;
line-height: 34px;
height: 34px;
margin-right: -1px;
margin-top: -1px;
padding: 0;
text-align: center;
width: 34px;
}
.square:focus {
outline: none;
}
.kbd-navigation .square:focus {
background: #ddd;
}
.game {
display: flex;
flex-direction: row;
}
.game-info {
margin-left: 20px;
}
- js文件内容如下 :
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
// 【棋盘中的方格】
// 注意函数的写法,
// 1、function打头,不需要内render方法,JSX语法直接return;
// 2、通过对象传参,而不是dom对象value属性:{this.props.value}
// 3、事件响应函数{props.onClick},而不是直接定义函数:{function() { ...; }} 或 {() => {...;}}
// 4、函数没有生命周期,也无法管理状态,意味着,在函数的组件上定义点击事件,在函数内部定义一个子函数,去修改一个内部变量,这个变量的变化,并不会刷新视图
function Square(props) {
// 函数写法,无构造方法,无state,避免每个小组件维护数据(同步数据),数据应该交给大脑,中心,更高级别的组件
return (
<button className="square" onClick={props.onClick}>
{props.filled}
</button>
);
}
// 【棋盘组件】
class Board extends React.Component {
// 本组件仍无state,虽然它表示一个棋盘,但是涉及到多个历史(即多个棋盘)的数据互动,则需要进一步抽象,提升等级
// 内部子函数,封装JSX片段
renderSquare(i) {
return (<Square filled={this.props.squares[i]}
onClick={()=>this.props.onClick(i)}
/>);
}
// reder告诉虚拟dom渲染内容
render() {
return (
<div>
<div className="board-row">
{this.renderSquare(0)}
{this.renderSquare(1)}
{this.renderSquare(2)}
</div>
<div className="board-row">
{this.renderSquare(3)}
{this.renderSquare(4)}
{this.renderSquare(5)}
</div>
<div className="board-row">
{this.renderSquare(6)}
{this.renderSquare(7)}
{this.renderSquare(8)}
</div>
</div>
);
}
}
class Game extends React.Component {
constructor(props) {
// 定义其子类的构造函数时,都需要调用 super 方法
super(props);
// 定义数据结构,初始化
this.state = {
// squares(棋盘)已经是二维数组,因此外层用一个对象封装,再封装成对象数组(棋盘历史)
// 初始化第一个棋盘,里面啥也没填
boards:[
{
squares: Array(9).fill(null),
}
],
stepNumber: 0, // 当前显示的棋盘下标
xIsNext: true,
}
}
handleClick(i) {
// 从已有的数组中返回选定的元素,slice(start,end),不包括end元素
const currentBoards = this.state.boards.slice(0, this.state.stepNumber + 1);
// jumpto按钮后,改变了state,导致stepNumber的变化,进而render,棋盘重新渲染,这没有问题
// 但是,如果棋盘的数据没有对应上,棋盘上的点击事件,则不一定会响应,因为你点击的格子可能已经有数据
// 因此,不能用下面这一行,这种写法,是固定拿最新的棋盘集
// const duplicatedBoards = this.state.boards.slice();
// 当前棋盘
const currentBoard = currentBoards[currentBoards.length - 1];
const duplicatedSquares = currentBoard.squares.slice();
// 计算获胜者,或者已经被填满(非null),则不再执行后续内容
if (calculateWinner(duplicatedSquares) || duplicatedSquares[i]) {
return;
}
// 增加棋子,形成一个新的棋盘
duplicatedSquares[i] = this.state.xIsNext ? 'X' : 'O';
// 每次点击棋盘中的方格,导致数据的变化,然后驱动render方法的再次执行
this.setState({
// 当前棋盘下标,连续点击的话,是一路从0加上来
stepNumber: currentBoards.length,
xIsNext: !this.state.xIsNext,
// 当前棋盘集合追加一个新棋盘,concat连接两个或多个数组,返回一个副本
// array1.concat(array2,array3,...,arrayX) https://www.runoob.com/jsref/jsref-concat-array.html
// 赋值棋盘集合
boards: currentBoards.concat([{
squares: duplicatedSquares,
}]),
});
}
jumpTo(step) {
// 外部按钮,导致数据的变化,也会更新view
this.setState({
stepNumber: step,
xIsNext : (step % 2) === 0
});
}
render() {
const boards = this.state.boards;
// 渲染指定棋盘到Board组件
const board = boards[this.state.stepNumber];
// 数组的用法array.map(function(currentValue当前元素,index下标,arr当前元素属于的数据组象,可选), thisValue回调函数的this指针,可选)
// https://www.runoob.com/jsref/jsref-map.html
const steps = boards.map(
// 每次render都会遍历渲染一遍
(board, index) => {
const desc = index ? 'go to move #' + index : 'go to game start'; // index为0或其它时,显示start
// li会以数字打头,比如1.
// react要求每个li要有key:https://reactjs.org/docs/lists-and-keys.html#keys
return (<li key={index}><button onClick={()=>this.jumpTo(index)}>{desc}</button></li>);
}
);
// 计算页面文字应该显示的信息
let status;
const winner = calculateWinner(board.squares); // 在click事件里也计算了一遍,是否可以考虑用state的变量来记录赢者?
if (winner) {
status = 'Winner: ' + winner;
} else {
status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O');
}
return (
<div className="game">
<div className="game-board">
<Board
squares = { // squares的变化,导致界面的变化
board.squares // current变量可以直接引用
}
onClick = {
(i) => this.handleClick(i) // 最后会转化为棋盘格子dom对象的click事件,这个i是Board的render里硬编码的,从0开始
}
/>
</div>
<div className="game-info">
<div>{status}</div>
<ol>{steps}</ol>
<ol><li>abc</li><li>def</li></ol>
</div>
</div>
);
}
}
// ========================================
ReactDOM.render(
<Game />,
document.getElementById('root')
);
function calculateWinner(squares) {
const lines = [
[0,1,2],
[3,4,5],
[6,7,8],
[2,4,6],
[0,4,8],
[0,3,6],
[1,4,7],
[2,5,8],
];
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;
}