1
0
mirror of https://github.com/apachecn/eloquent-js-3e-zh.git synced 2025-05-23 20:02:20 +00:00
This commit is contained in:
wizardforcel 2018-05-13 12:10:47 +08:00
parent 39306cd144
commit 70e1e2aa82

120
19.md
View File

@ -38,9 +38,9 @@
这种方法有许多变种,每个变种都有自己的好处和问题,但它们的中心思想是一样的:状态变化应该通过明确定义的渠道,而不是遍布整个地方。
我们的组件将是与界面一致的类。 他们的构造函数被赋予一个状态,它可能是整个应用状态,或者如果它不需要访问所有东西,是一些较小的值,并使用它构建一个`dom`属性,也就是表示组件的 DOM。 大多数构造函数还会接受一些其他值,这些值不会随着时间而改变,例如它们可用于分派操作的函数。
我们的组件将是与界面一致的类。 他们的构造被赋予一个状态,它可能是整个应用状态,或者如果它不需要访问所有东西,是一些较小的值,并使用它构建一个`dom`属性,也就是表示组件的 DOM。 大多数构造还会接受一些其他值,这些值不会随着时间而改变,例如它们可用于分派操作的函数。
每个组件都有一个`setState`方法,用于将其同步到新的状态值。 该方法接受一个参数,该参数的类型与构造函数的第一个参数的类型相同。
每个组件都有一个`setState`方法,用于将其同步到新的状态值。 该方法接受一个参数,该参数的类型与构造的第一个参数的类型相同。
## 状态
@ -72,7 +72,7 @@ class Picture {
我们希望能够将图片当做不变的值,我们将在本章后面回顾其原因。 但是我们有时也需要一次更新大量像素。 为此,该类有`draw`方法,接受更新后的像素(具有`x``y``color`属性的对象)的数组,并创建一个覆盖这些像素的新图像。 此方法使用不带参数的`slice`来复制整个像素数组 - 切片的起始位置默认为 0结束位置为数组的长度。
`empty `方法使用我们以前没有见过的两个数组功能。 可以使用数字调用`Array`构造函数来创建给定长度的空数组。 然后`fill`方法可以用于使用给定值填充数组。 这些用于创建一个数组,所有像素具有相同颜色。
`empty `方法使用我们以前没有见过的两个数组功能。 可以使用数字调用`Array`构造来创建给定长度的空数组。 然后`fill`方法可以用于使用给定值填充数组。 这些用于创建一个数组,所有像素具有相同颜色。
颜色存储为字符串,包含传统 CSS 颜色代码 - 一个井号(`#`),后跟六个十六进制数字,两个用于红色分量,两个用于绿色分量,两个用于蓝色分量。这是一种有点神秘而不方便的颜色编写方法,但它是 HTML 颜色输入字段使用的格式,并且可以在`canva`s绘图上下文的`fillColor`属性中使用,所以对于我们在程序中使用颜色的方式,它足够实用。
@ -218,3 +218,117 @@ PictureCanvas.prototype.touch = function(startEvent,
```
对于触摸事件,`clientX``clientY`不能直接在事件对象上使用,但我们可以在`touches`属性中使用第一个触摸对象的坐标。
## 应用
为了能够逐步构建应用,我们将主要组件实现为画布周围的外壳,以及一组动态工具和控件,我们将其传递给其构造器。
控件是出现在图片下方的界面元素。 它们为组件构造器的数组而提供。
工具是绘制像素或填充区域的东西。 该应用将一组可用工具显示为`<select>`字段。 当前选择的工具决定了,当用户使用指针设备与图片交互时,发生的事情。 它们作为一个对象而提供,该对象将出现在下拉字段中的名称,映射到实现这些工具的函数。 这个函数接受图片位置,当前应用状态和`dispatch`函数作为参数。 它们可能会返回一个移动处理器,当指针移动到另一个像素时,使用新位置和当前状态调用该函数。
```js
class PixelEditor {
constructor(state, config) {
let {tools, controls, dispatch} = config;
this.state = state;
this.canvas = new PictureCanvas(state.picture, pos => {
let tool = tools[this.state.tool];
let onMove = tool(pos, this.state, dispatch);
if (onMove) return pos => onMove(pos, this.state);
});
this.controls = controls.map(
Control => new Control(state, config));
this.dom = elt("div", {}, this.canvas.dom, elt("br"),
...this.controls.reduce(
(a, c) => a.concat(" ", c.dom), []));
}
setState(state) {
this.state = state;
this.canvas.setState(state.picture);
for (let ctrl of this.controls) ctrl.setState(state);
}
}
```
指定给`PictureCanvas`的指针处理器,使用适当的参数调用当前选定的工具,如果返回了移动处理器,使其也接收状态。
所有控件在`this.controls`中构造并存储,以便在应用状态更改时更新它们。 `reduce`的调用会在控件的 DOM 元素之间引入空格。 这样他们看起来并不那么密集。
第一个控件是工具选择菜单。 它创建`<select>`元素,每个工具带有一个选项,并设置`"change"`事件处理器,用于在用户选择不同的工具时更新应用状态。
```js
class ToolSelect {
constructor(state, {tools, dispatch}) {
this.select = elt("select", {
onchange: () => dispatch({tool: this.select.value})
}, ...Object.keys(tools).map(name => elt("option", {
selected: name == state.tool
}, name)));
this.dom = elt("label", null, "🖌 Tool: ", this.select);
}
setState(state) { this.select.value = state.tool; }
}
```
通过将标签文本和字段包装在`<label>`元素中,我们告诉浏览器该标签属于该字段,例如,您可以点击标签来聚焦该字段。
我们还需要能够改变颜色 - 所以让我们添加一个控件。 `type`属性为颜色的 HTML `<input>`元素为我们提供了专门用于选择颜色的表单字段。 这种字段的值始终是`"#RRGGBB"`格式(红色,绿色和蓝色分量,每种颜色两位数字)的 CSS 颜色代码。 当用户与它交互时,浏览器将显示一个颜色选择器界面。
该控件创建这样一个字段,并将其连接起来,与应用程序状态的`color`属性保持同步。
```js
class ColorSelect {
constructor(state, {dispatch}) {
this.input = elt("input", {
type: "color",
value: state.color,
onchange: () => dispatch({color: this.input.value})
});
this.dom = elt("label", null, "🎨 Color: ", this.input);
}
setState(state) { this.input.value = state.color; }
}
```
## 绘图工具
在我们绘制任何东西之前,我们需要实现一些工具,来控制画布上的鼠标或触摸事件的功能。
最基本的工具是绘图工具,它可以将你点击或轻触的任何像素,更改为当前选定的颜色。 它分派一个动作,将图片更新为一个版本,其中所指的像素赋为当前选定的颜色。
```js
function draw(pos, state, dispatch) {
function drawPixel({x, y}, state) {
let drawn = {x, y, color: state.color};
dispatch({picture: state.picture.draw([drawn])});
}
drawPixel(pos, state);
return drawPixel;
}
```
该函数立即调用`drawPixel`函数,但也会返回它,以便在用户在图片上拖动或滑动时,再次为新的所触摸的像素调用。
为了绘制较大的形状,可以快速创建矩形。 矩形工具在开始拖动的点和拖动到的点之间画一个矩形。
```js
function rectangle(start, state, dispatch) {
function drawRectangle(pos) {
let xStart = Math.min(start.x, pos.x);
let yStart = Math.min(start.y, pos.y);
let xEnd = Math.max(start.x, pos.x);
let yEnd = Math.max(start.y, pos.y);
let drawn = [];
for (let y = yStart; y <= yEnd; y++) {
for (let x = xStart; x <= xEnd; x++) {
drawn.push({x, y, color: state.color});
}
}
dispatch({picture: state.picture.draw(drawn)});
}
drawRectangle(start);
return drawRectangle;
}
```