diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 84f5661..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 13745c9..c1d09b1 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,7 @@ jspm_packages ./code/node_modules ./code/*.log + +.DS_Store + +*~ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..afbc4ed --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,6 @@ +# Contributting to React in Patterns (Thai Edition) +หนังสือ "React in Patterns (ฉบับภาษาไทย)" ได้ขออนุญาตกับทางผู้เขียนเพื่อทำการแปลเป็นภาษาไทยเรียบร้อยแล้ว หากต้องการร่วม contribute หรือแก้ไขข้อผิดพลาดประการใด ทางทีมผู้แปลยินดีเป็นอย่างยิ่ง + +## แปลไทยโดย +- [React Bangkok](https://github.com/reactbkk) +- [Athiwat Hirunworawongkun](https://github.com/athivvat) \ No newline at end of file diff --git a/README.md b/README.md index 74b1c53..830033c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ -# React in patterns +# React in patterns (ฉบับภาษาไทย) -:books: A free book that talks about design patterns/techniques used while developing with [React](https://facebook.github.io/react/). +:books: หนังสือแจกฟรีที่อธิบายเกี่ยวกับดีไซน์แพตเทิร์นและเทคนิคที่ใช้สำหรับการพัฒนาเว็บแอพพลิเคชันด้วย [React](https://facebook.github.io/react/). -## Book +## ผู้เขียน +[Krasimir Tsonev](https://github.com/krasimir) + +## หนังสือ * [GitBook](https://www.gitbook.com/book/krasimir/react-in-patterns/details) * [Web](https://krasimir.gitbooks.io/react-in-patterns/content/) @@ -12,11 +15,11 @@ ![React in patterns cover](./book/cover_small.jpg) -## Content +## สารบัญ -* [In brief](./book/chapter-1/README.md) +* [เนื้อหาโดยสังเขป](./book/chapter-1/README.md) -### Foundation +### หลักการพื้นฐาน (Foundation) * [Communication](./book/chapter-2/README.md) * [Input](./book/chapter-2/README.md#input) @@ -30,7 +33,7 @@ * [Controlled and uncontrolled inputs](./book/chapter-5/README.md) * [Presentational and container components](./book/chapter-6/README.md) -### Data flow +### กระแสข้อมูล (Data flow) * [One direction data flow](./book/chapter-7/README.md) * [Flux](./book/chapter-8/README.md) @@ -54,11 +57,11 @@ * [Integration of third-party libraries](./book/chapter-12/README.md) -## Source code +## ซอร์สโค้ด -The code samples used in the book are available [here](./code). +ตัวอย่างซอร์สโค้ดที่ใช้ประกอบในหนังสือเล่มนี้ สามารถดาวน์โหลดได้ที่ [ตัวอย่างซอร์สโค้ด](./code). -## Other resources +## แหล่งข้อมูลอ้างอิงอื่นๆ * [React Design principles](https://facebook.github.io/react/contributing/design-principles.html) * [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/tree/master/react) diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/assets/cover.jpg b/assets/cover.jpg index eb7caa2..65da89f 100644 Binary files a/assets/cover.jpg and b/assets/cover.jpg differ diff --git a/assets/cover.psd b/assets/cover.psd index 9c37f35..e244478 100644 Binary files a/assets/cover.psd and b/assets/cover.psd differ diff --git a/book/chapter-1/README.md b/book/chapter-1/README.md index a5ade24..fdec633 100644 --- a/book/chapter-1/README.md +++ b/book/chapter-1/README.md @@ -1,7 +1,7 @@ -# In brief -This cookbook is targeting developers that already have basic understanding of what React is and how it works. It's not meant to be used as a complete how-to guide but as an introduction to popular concepts/design patterns. Paradigms that more or less are introduced by the community. It points you to an abstract thinking. For example, instead of talking about Flux, it talks about data flow. Instead of talking about higher-order components it talks about composition. +# เนื้อหาโดยสังเขป -The book is highly opinioned. It represents my own understanding of the described patterns and it is possible that they have a different interpretation around the web. Have this in mind when arguing with someone and using this book as an argument. - -Also notice that English is not my native language. If you see a typo or something sounds weird please contribute here [github.com/krasimir/react-in-patterns](https://github.com/krasimir/react-in-patterns/tree/master/book). If you read from a printed version of this book then feel free to use a pen ¯\\_(ツ)_/¯ + หนังสือเล่มนี้เล็งเป้าหมายไปที่นักเขียนโปรแกรมที่มีความรู้ความเข้าใจพื้นฐานรวมถึงการทำงานของ React อยู่แล้ว โดยที่ไม่ได้หวังว่าจะหนังสือเล่มนี้จะเป็นหนังสือที่กำหนดแนวทางในการเขียนโปรแกรม แต่หวังว่าจะเป็นจุดเริ่มต้นในการศึกษา แนวคิด/รูปแบบ ของการเขียน React + ตัวอย่างไม่มากก็น้อยที่ถูกนำเสนอโดยชุมชนนักพัฒนาชี้ให้เห็นถึงการคิดแบบเชิงนามธรรม เช่น แทนที่จะพูดเกี่ยวกับ Flux หนังสือเล่มนี้จะพูดถึง data flow หรือแทนที่จะพูดถึง higher-order components หนังสือเล่มนี้จะพูดถึง composition แทน + + หนังสือเล่มนี้ค่อนข้างเป็นมุมมองส่วนตัว ซึ่งแสดงถึงความเข้าใจของผู้เขียนต่อรูปแบบการเขียนโปรแกรมที่ถูกกล่าวถึง เพราะฉะนั้นเป็นไปได้ที่จะมีการตีความที่แตกต่างแบบอื่น ๆ อยู่ทั่วไปในอินเตอร์เน็ต ขอให้ผู้อ่านทั้งหลายโปรดระลึกถึงสิ่งนี้ไว้ด้วยหากท่านจะใช้หนังสือเล่มนี้เป็นข้ออ้างอิงในการโต้แย้งใด ๆ diff --git a/book/chapter-10/README.md b/book/chapter-10/README.md index e4a07eb..6618de8 100644 --- a/book/chapter-10/README.md +++ b/book/chapter-10/README.md @@ -1,8 +1,8 @@ -# Dependency injection +# การส่งต่อ dependency (Dependency injection) -Big part of the modules/components that we write have dependencies. A proper management of these dependencies is critical for the success of the project. There is a technique (most people consider it a *pattern*) called [*dependency injection*](http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) that helps solving the problem. +Components หรือ modules ที่ถูกเขียนขึ้นมาส่วนใหญ่มักจะมี dependencies ติดมาด้วยเสมอ การที่เราสามารถจัดการ dependencies เหล่านั้น จึงเป็นส่วนสำคัญที่ทำให้โปรเจคของเราสำเร็จลุล่วงไปด้วยดี ปัจจุบัน มีเทคนิคชนิดหนึ่ง (หรือที่หลาย ๆ คนเรียกว่า *pattern*) ที่สามารถช่วยจัดการ dependencies ของเราได้ นั้นก็คือ [*dependency injection*](http://krasimirtsonev.com/blog/article/Dependency-injection-in-JavaScript) -In React the need of dependency injector is easily visible. Let's consider the following application tree: +ใน React เราสามารถมองได้ง่าย ๆ ว่าส่วนไหนที่ต้องการ dependency injector (หรือ ส่วนที่ต้องการใช้ dependency) ยกตัวอย่างเช่น application tree ด้านล่าง ```js // Title.jsx @@ -35,9 +35,9 @@ class App extends React.Component { }; ``` -The string "React in patterns" should somehow reach the `Title` component. The direct way of doing this is to pass it from `App` to `Header` and then `Header` pass it down to `Title`. However, this may work for these three components but what happens if there are multiple properties and deeper nesting. Lots of components will act as proxy passing properties to their children. +จากโค้ดตัวอย่าง จะเห็นได้ว่าค่าสตริง "React in patterns" จะต้องถูกส่งไปหา `Title` component โดยที่วิธีการตรง ๆ เลย คือการส่งค่าผ่าน props จาก `App` ไปยัง `Header` และจาก `Header` ไปยัง `Title` สำหรับ components สามตัวอาจจะไม่ใช้เรื่องแปลก แต่หาก components ที่เราต้องทำงานด้วยนั้น มี props ที่หลากหลาย และ มี component ที่ซ้อนกันหลาย ๆ ชั้น จะต้องมี components ระหว่างทางหลายตัวที่จะได้รับค่าไป เพียงเพื่อโยนไปให้ตัวลูกโดยที่ตัวเองไม่ได้ใช้ -We already saw how the [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components) may be used to inject data. Let's use the same technique to inject the `title` variable: +จากที่ผ่านมา เราได้เห็นแล้วว่า [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components) นั้นสามารถใช้สำหรับการส่งค่าลงไปได้ เราจะมาลองใช้เทคนิคที่ว่ากับตัวแปร `title` ดู ```js // inject.jsx @@ -71,16 +71,16 @@ export default function Header() { } ``` -The `title` is hidden in a middle layer (higher-order component) where we pass it as a prop to the original `Title` component. That's all nice but it solves only half of the problem. Now we don't have to pass the `title` down the tree but how this data reaches the `inject.jsx` helper. +ตอนนี้ตัวแปร `title` ได้ถูกซ่อนอยู่ในเลเยอร์ตรงกลาง (higher-order component) จากตรงนั้นมันจะถูกส่งไปหา `Title` component ผ่าน props โดยตรง ท่านี้แก้ปัญหาได้ครึ่งทาง เราไม่ต้องกังวลเรื่องการส่ง `title` ลงไปหลาย ๆ ชั้นอีกแล้ว แต่ยังมีปัญหาเรื่องที่ว่า เราจะทำยังไงให้ค่าวิ่งไปหา `inject.jsx` -## Using React's context (prior v. 16.3) +## การใช้ React's context (เวอร์ชั่นก่อนหน้า 16.3) -*In v16.3 React's team introduced a new version of the context API and if you are going to use that version or above you'd probably skip this section.* +*ในเวอร์ชั่น 16.3, ทีมผู้พัฒนา React ได้ เสนอ context API ตัวใหม่ และ สำหรับคนที่คิดว่าจะใช้ เวอร์ชั่น 16.3 หรือ มากกว่า สามารถข้ามส่วนนี้ไปได้เลย* -React has the concept of [*context*](https://facebook.github.io/react/docs/context.html). The *context* is something that every React component has access to. It's something like an [event bus](https://github.com/krasimir/EventBus) but for data. A single *store* which we access from everywhere. +ในโลกของ React นั้น มีแนวคิดที่ชื่อว่า [*context*](https://facebook.github.io/react/docs/context.html) ซึ่ง context นั้นคือสิ่ง ๆ หนึ่งที่ React component ทุกตัวสามารถหยิบมาใช้ได้ แนวคิดของ context นั้นจะคล้าย ๆ กับ [event bus](https://github.com/krasimir/EventBus) สำหรับการส่งข้อมูล หรือ *store* อันหนึ่ง ที่สามารถเข้าถึงจากที่ไหนก็ได้ ```js -// a place where we will define the context +// จุดที่เราทำการประกาศ context var context = { title: 'React in patterns' }; class App extends React.Component { @@ -93,7 +93,7 @@ App.childContextTypes = { title: React.PropTypes.string }; -// a place where we use the context +// จุดที่เราจะใช้ context class Inject extends React.Component { render() { var title = this.context.title; @@ -105,7 +105,7 @@ Inject.contextTypes = { }; ``` -Notice that we have to specify the exact signature of the context object. With `childContextTypes` and `contextTypes`. If those are not specified then the `context` object will be empty. That may be a little bit frustrating because we may have lots of stuff to put there. That is why it is a good practice that our `context` is not just a plain object but it has an interface that allows us to store and retrieve data. For example: +จะเห็นได้จากโค้ดด้านบน ว่าเราจะต้องประกาศ object context พร้อมตัวแปรที่เราจะใช้ ผ่าน `childContextTypes` และ `contextTypes` ถ้าเราไม่ประกาศ object `context` จะมาเป็น object เปล่า ๆ ซึ่งบางครั้งก็อาจจะทำให้รู้สึกหงุดหงิด ที่ต้องมานั่งใส่ตัวแปรหลาย ๆ ตัวลงไปในนั้น เพราะฉะนั้นวิธีการที่ดีคือ การเปลี่ยน `context` ให้มี interface ที่สามารถเก็บและส่งค่าได้ ดังตัวอย่างด้านล่าง ```js // dependencies.js @@ -119,7 +119,8 @@ export default { } } ``` -Then, if we go back to our example, the `App` component may look like that: + +ถ้านำกลับไปใช้กับตัวอย่างเดิม หน้าตาของ `App` component จะเป็นเหมือนด้านล่าง: ```js import dependencies from './dependencies'; @@ -141,7 +142,7 @@ App.childContextTypes = { }; ``` -And our `Title` component gets it's data through the context: +และ `Title` component ของเรา จะสามารถนำค่าจาก context ออกมาใช้ได้ ```js // Title.jsx @@ -157,7 +158,8 @@ Title.contextTypes = { }; ``` -Ideally we don't want to specify the `contextTypes` every time when we need an access to the context. This detail may be wrapped again in a higher-order component. And even better, we may write an utility function that is more descriptive and helps us declare the exact wiring. I.e instead of accessing the context directly with `this.context.get('title')` we ask the higher-order component to get what we need and pass it as props to our component. For example: +ตามหลักการแล้ว เราไม่อยากที่จะนั่งประกาศ `contextTypes` ในทุก ๆ ครั้งที่เราอยากจะเข้าถึง context ในส่วนนี้เราสามารถนำ higher-order component มาครอบได้ และที่ดีกว่าคือเราสามารถเขียน utility function ที่มีความหมายชัดเจนมากกว่า และ ช่วยให้เราสามารถต่อ context ได้อย่างถูกต้อง ยกตัวอย่าง เช่น แทนที่เราจะเข้าถึง context ตรง ๆ ผ่าน `this.context.get('title')` เราสามารถขอ context ผ่าน higher-order component และให้ higher-order component ส่งค่ามาในรูปแบบของ props แทน ดังตัวอย่างด้านล่าง: + ```js // Title.jsx @@ -172,9 +174,10 @@ export default wire(Title, ['title'], function resolve(title) { }); ``` -The `wire` function accepts a React component, then an array with all the needed dependencies (which are `register`ed already) and then a function which I like to call `mapper`. It receives what is stored in the context as a raw data and returns an object which is later used as props for our component (`Title`). In this example we just pass what we get - a `title` string variable. However, in a real app this could be a collection of data stores, configuration or something else. +ฟังก์ชัน `wire` รับ React component, array ที่ประกอบด้วย dependencies (ที่`เชื่อมต่อ`เข้ากับ context แล้ว) ที่เราต้องการจะเรียกใช้ และ ฟังก์ชันที่ผู้เขียนชอบเรียกว่า `mapper` ซึ่งฟังก์ชันนี้จะรับค่ามาจาก context และ return ค่าในรูปแบบของ object โดยที่ object นั้นท้ายที่สุดแล้วจะถูกส่งให้ component ของเรา (`Title`) ในรูปแบบของ props ดังที่เห็นในตัวอย่างนี้ เรานำตัวแปร `title` ส่งเข้าไป +ในการเขียนแอปจริง ๆ ค่าที่ส่งเข้าไปอาจจะเป็น กลุ่มข้อมูลหลาย ๆ อัน, configuration หรือ อื่น ๆ -Here is how the `wire` function looks like: +และนี้คือหน้าตาของฟังก์ชัน `wire`: ```js export default function wire(Component, dependencies, mapper) { @@ -197,15 +200,15 @@ export default function wire(Component, dependencies, mapper) { }; ``` -`Inject` is a higher-order component that gets access to the context and retrieves all the items listed under `dependencies` array. The `mapper` is a function receiving the `context` data and transforms it to props for our component. +`Inject` คือ higher-order component ที่สามารถเข้าถึง context และ นำค่าที่ประกาศไว้ใน array `dependencies` ออกมา ส่วนฟังก์ชัน `mapper` ทำหน้าที่รับ `context` เหล่านั้น และ ส่งเข้าไปหา component ของเราผ่าน props -## Using React's context (v. 16.3 and above) +## การใช้ React's context (เวอร์ชั่น 16.3 หรือ มากกว่า) -For years the context API was not really recommended by Facebook. They mentioned in the official docs that the API is not stable and may change. And that is exactly what happened. In the version 16.3 we got a new one which I think is more natural and easy to work with. +เป็นเวลาหลายปีที่ Facebook ไม่แนะนำให้ใช้ context API โดยให้เหตุผลไว้ใน official docs ว่า API นั้น ไม่เสถียร และ เสี่ยงต่อการเปลี่ยนแปลงในอนาคต และนั้นคือสิ่งที่เกิดขึ้นในปัจจุบัน ในเวอร์ชั่น 16.3 เราได้ API อันใหม่ ซึ่งผู้เขียนคิดว่า API นี้เป็นธรรมชาติมากขึ้น และ ใช้งานได้ง่ายกว่า -Let's use the same example with the string that needs to reach a `` component. +สมมุติว่าเราลองนำตัวอย่างเดิมมาใช้ ตัวอย่างที่เราต้องการส่งสตริงไปหา `<Title>` component -We will start by defining a file that will contain our context initialization: +เราเริ่มโดยการสร้างไฟล์สำหรับการสร้าง context ```js // context.js @@ -217,9 +220,9 @@ export const Provider = Context.Provider; export const Consumer = Context.Consumer; ``` -`createContext` returns an object that has `.Provider` and `.Consumer` properties. Those are actually valid React classes. The `Provider` accepts our context in the form of a `value` prop. The consumer is used to access the context and basically read data from it. And because they usually live in different files it is a good idea to create a single place for their initialization. +ฟังก์ชัน `createContext` returns object ตัวหนึ่ง ที่มี properties ประกอบด้วย `.Provider` และ `.Consumer` โดยที่สองตัวนี้จริง ๆ แล้วคือ React class และสำหรับตัว Provider นั้น จะรับ context ผ่าน props ชื่อ `value` ในขณะที่ตัว consumer นั้น จะใช้สำหรับการเข้าถึงและอ่านค่า context ปกติแล้วสองตัวนี้จะอยู่คนละไฟล์ มันจึงเป็นความคิดที่ดี ที่จะสร้างที่ ๆ หนึ่งสำหรับการสร้างสองตัวนั้น -Let's say that our `App` component is the root of our tree. At that place we have to pass the context. +สมมุติว่า `App` component ของเรานั้นคือจุดสูงสุดของ application tree ข้างในนั้นเราจะทำการส่ง context เข้าไป ```js import { Provider } from './context'; @@ -237,7 +240,7 @@ class App extends React.Component { }; ``` -The wrapped components and their children now share the same context. The `<Title>` component is the one that needs the `title` string so that is the place where we use the `<Consumer>`. +ตอนนี้ components ที่โดยครอบและลูก ๆ ของมันได้ถูกแชร์ context อันเดียวกัน และ `<Title>` component คือตัวที่ต้องการสตริง `title` ตรงนี้จึงเป็นที่ ๆ เราจะนำ `<Consumer>` มาใช้ ```js import { Consumer } from './context'; @@ -251,23 +254,23 @@ function Title() { } ``` -*Notice that the `Consumer` class uses the function as children (render prop) pattern to deliver the context.* +*สังเกตุได้ว่า `Consumer` class ใช้ function as children (render prop) pattern สำหรับการส่ง context* -The new API feels easier to understand and eliminates the boilerplate. It is still pretty new but looks promising. It opens a whole new range of possibilities. +API อันใหม่นั้น ง่ายต่อการเข้าใจ ทั้งยังทำให้เราไม่ต้องใช้ boilerplate สำหรับตัว API นั้นค่อนข้างใหม่แต่ดูมีแนวโน้มที่ดี มันเปิดโอกาสให้เราเข้าถึงความเป็นไปได้ที่หลากหลายมากขึ้น -## Using the module system +## การใช้ module system -If we don't want to use the context there are a couple of other ways to achieve the injection. They are not exactly React specific but worth mentioning. One of them is using the module system. +ถ้าเราไม่ต้องการที่จะใช้ context ก็มีทางเลือกอื่นที่สามารถทำให้เราทำ injection ได้ โดยที่ทางเลือกนั้นอาจจะไม่เจาะจงไปที่ React แต่ก็ควรค่าแก่การกล่าวถึง หนึ่งในนั้นคือการใช้ module system -As we know the typical module system in JavaScript has a caching mechanism. It's nicely noted in the [Node's documentation](https://nodejs.org/api/modules.html#modules_caching): +อย่างที่รู้ ๆ กันว่า ปกติแล้ว module system ใน Javascript นั้นมีกลไกการทำ caching โดยได้มีการโน้ตไว้ใน [Node's documentation](https://nodejs.org/api/modules.html#modules_caching): -> Modules are cached after the first time they are loaded. This means (among other things) that every call to require('foo') will get exactly the same object returned, if it would resolve to the same file. +> Modules นั้นจะถูก cached หลังจากที่มันถูกโหลดขึ้นมาครั้งแรก นั้นหมายความว่า ทุกครั้งที่เราเรียก required('foo') object อันเดิมจะถูกนำมาใช้เสมอถ้ามัน resolve ไปหาไฟล์อันเดิม -> Multiple calls to require('foo') may not cause the module code to be executed multiple times. This is an important feature. With it, "partially done" objects can be returned, thus allowing transitive dependencies to be loaded even when they would cause cycles. +> การเรียกไปหา require('foo') หลาย ๆ ครั้ง จะไม่ทำให้โค้ดข้างใน foo module ถูกเรียกใหม่ซ้ำ ๆ นี้เป็นฟีเจอร์ที่สำคัญมากเพราะว่า "partially done" object (object ที่ยังรันไม่เสร็จ แต่ถูก require) จะถูก return ออกมาได้ และ ทำให้ transitive dependencies (dependency ตอนที่ modules require กันเอง) ถูกโหลดโดยไม่ทำให้เกิดลูป (cyclic dependency) -How is that helping for our injection? Well, if we export an object we are actually exporting a [singleton](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) and every other module that imports the file will get the same object. This allows us to `register` our dependencies and later `fetch` them in another file. +แล้วสิ่งเหล่านี้จะช่วยเราในการทำ injection อย่างไร ? มันช่วยเราได้เพราะ object ที่ถูก export ออกมานั้น จริง ๆ แล้วคือ [singleton](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) และทุก module ที่ import ไฟล์นั้นเข้าไป ก็จะเข้าถึงอ็อบเจกต์ตัวเดียวกัน นั้นทำให้เราสามารถ ใส่ dependencies ของเราลงไป (`register`) และ นำออกมาจากไฟล์อื่น ๆ ได้ (`fetch`) -Let's create a new file called `di.jsx` with the following content: +เราลองมาสร้างไฟล์ใหม่ชื่อ `di.jsx` ที่มีคอนเทนต์ตามด้านล่าง: ```js var dependencies = {}; @@ -300,9 +303,9 @@ export function wire(Component, deps, mapper) { } ``` -We'll store the dependencies in `dependencies` global variable (it's global for our module, not for the whole application). We then export two functions `register` and `fetch` that write and read entries. It looks a little bit like implementing setter and getter against a simple JavaScript object. Then we have the `wire` function that accepts our React component and returns a [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components). In the constructor of that component we are resolving the dependencies and later while rendering the original component we pass them as props. We follow the same pattern where we describe what we need (`deps` argument) and extract the needed props with a `mapper` function. +เราะจะเก็บ dependecies ไว้ในตัวแปร global ชื่อ `dependencies` (ตัวแปร global ในระดับ module ไม่ใช้ระดับแอปพลิเคชัน) หลังจากนั้นเราจะ export สองฟังก์ชันได้แก่ `register` และ `fetch` ที่จะทำหน้าที่เขียนและอ่านค่าต่าง ๆ โดยที่มันจะคล้าย ๆ กับการสร้าง setter และ getter ใน object ของ Javascript ต่อจากนั้นเราจะใช้ฟังก์ชัน `wire` ในการรับ React component และ return [higher-order component](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components) ออกไป และใน constructor ของ component ที่อยู่ข้างในฟังก์ชัน wire เราจะทำการดึง dependencies ออกมา แล้วก็ส่งมันลงไปหา component ข้างใต้ที่กำลัง render ในรูปแบบของ props โดยที่เราจะทำตาม pattern เดิมที่เราอธิบายสิ่งที่เราต้องการ (`deps` argument) และดึง props ที่ต้องการออกมาผ่านฟังก์ชัน `mapper` -Having the `di.jsx` helper we are again able to register our dependencies at the entry point of our application (`app.jsx`) and inject them wherever (`Title.jsx`) we need. +การที่เรามี `di.jsx` helper นั้นทำให้เราสามารถสร้าง dependencies ได้ที่จุดเริ่มต้นของแอปพลิเคชัน (`app.jsx`) และ ส่งมันลงไปในที่ ๆ เราต้องการได้ (`Title.jsx`) <span class="new-page"></span> @@ -346,8 +349,8 @@ export default wire( ); ``` -*If we look at the `Title.jsx` file we'll see that the actual component and the wiring may live in different files. That way the component and the mapper function become easily unit testable.* +*ถ้าเรามองที่ไฟล์ `Title.jsx` เราจะเห็นว่าตัว component และ ส่วนที่ทำการเชื่อมต่อนั้นสามารถอยู่คนละไฟล์ได้ ซึ่งท่านี้จะทำให้ตัว component และฟังก์ชัน mapper นั้นง่ายต่อการทำ unit test* -## Final thoughts +## ข้อคิด -Dependency injection is a tough problem. Especially in JavaScript. Lots of people didn't realize that but putting a proper dependency management is a key process of every development cycle. JavaScript ecosystem offers different tools and we as developers should pick the one that fits in our needs. +Dependency injection นั้นเป็นปัญหาที่ยากโดยเฉพาะใน Javascript หลาย ๆ คนไม่คำนึงถึงว่าการทำ dependency management ที่เหมาะสมนั้น เป็นกระบวนการสำคัญในทุก development cycle และในส่วนของ JavaScript ecosystem นั้น มี tools ที่หลากหลายมานำเสนอให้เราอยู่เสมอ และ เรา developers ควรที่จะเลือกหยิบสิ่งที่ตอบโจทย์ต่อความต้องการของเรามากที่สุด diff --git a/book/chapter-11/README.md b/book/chapter-11/README.md index c7b9da2..4169467 100644 --- a/book/chapter-11/README.md +++ b/book/chapter-11/README.md @@ -1,10 +1,10 @@ -# Styling React components +# การตกแต่ง React components -React is a view layer. As such it kind of controls the markup rendered in the browser. And we know that the styling with CSS is tightly connected to the markup on the page. There are couple of approaches for styling React applications and in this section we will go through the most popular ones. +React นั้นเป็นส่วนแสดงผล ซึ่งเป็นการควมคุม Markup ที่จะแสดงผลในเบราว์เซอร์ เรามักจะใช้ CSS ในการตกแต่งหน้า Markup ของเรา มีหลายวิธีมากในการจัดการกับ Styling บนแอพพลิเคชั่น React และในบทนี้ เราจะมาพูดถึงวิธีการที่นิยมกัน -## The good old CSS class +## CSS Class ที่ดีในทุกยุคสมัย -JSX syntax is pretty close to HTML syntax. As such we have almost the same tag attributes and we may still style using CSS classes. Classes which are defined in an external `.css` file. The only caveat is using `className` and not `class`. For example: +JSX Syntax นั้นมีความใกล้เคียงกับภาษา HTML ซึ่งนั่นก็คือเรายังคงใช้ Attribute ต่าง ๆ เหมือนกัน และเราอาจจะยังคงใช้ CSS Class ในการ Styling โดยที่ Class ต่างๆ ถูกประกาศจากไฟล์ `.css` โดยมีข้อแตกต่างอย่างนึงก็คือต้องใช้ `className` ไม่ใช่ `class` เช่น ``` <h1 className='title'>Styling</h1> @@ -12,7 +12,7 @@ JSX syntax is pretty close to HTML syntax. As such we have almost the same tag a ## Inline styling -The inline styling works just fine. Similarly to HTML we are free to pass styles directly via a `style` attribute. However, while in HTML the value is a string in JSX must be an object. +การทำ Inline CSS ก็สามารถทำได้ดีเช่นเดียวกับ HTML ที่เราสามารถส่งค่า Parameter ต่าง ๆ ได้โดยตรงผ่าน Attribute `style` แต่อย่างไรก็ตาม ใน JSX นั้นเราจะต้องกำหนด Styling ด้วย Object แตกต่างจาก HTML ที่กำหนดเป็น String ```js const inlineStyles = { @@ -25,7 +25,8 @@ const inlineStyles = { <h2 style={ inlineStyles }>Inline styling</h2> ``` -Because we write the styles in JavaScript we have some limitations from a syntax point of view. If we want to keep the original CSS property names we have to put them in quotes. If not then we have to follow the camel case convention. However, writing styles in JavaScript is quite interesting and may be a lot more flexible then the plain CSS. Like for example inheriting of styles: +เพราะว่าเราเขียน Style ใน Syntax ของ JavaScript เราจึงมีข้อจำกัดของ Syntax +หากเราต้องการเขียน CSS Property ในแบบของ CSS ดั้งเดิมนั้น เราจะต้องเขียนภายใน Quote ( เครื่องหมาย ", ' ) ถ้าไม่เช่นนั้นคุณก็จะต้องเขียนตามหลัก Camel Case อย่างไรก็ตาม Styling ใน JavaScript นั้นมีความน่าสนใจและยืดหยุ่นได้หลากหลายวิธีกว่า CSS ปกติทั่วไป (เช่น Plain CSS ใน HTML) ดังในตัวอย่างด้านล่างนี้ เราส่งผ่าน Property จาก Style หนึ่งไปยังอีก Style หนึ่ง: ```js const theme = { @@ -33,20 +34,20 @@ const theme = { color: 'blue' }; const paragraphText = { + // ES2018 object spread ...theme, fontSize: '20px' }; ``` -We have some basic styles in `theme` and with mix them with what is in `paragraphText`. Shortly, we are able to use the whole power of JavaScript to organize our CSS. What it matters at the end is that we generate an object that goes to the `style` attribute. +เรามี Style ชุดนึงใน `theme` และเราก็เรียกใช้มันภายใน Style ของ `paragraphText` อธิบายง่าย ๆ ก็คือ เราสามารถใช้ความสามารถของ JavaScript ในการจัดการ CSS ของเรา สิ่งที่เราต้องการให้คุณเห็นคือสุดท้ายเราได้สร้าง Object หนึ่ง ซึ่งมันจะไปแทรกตัวอยู่ใน Attribute `style` ## CSS modules -[CSS modules](https://github.com/css-modules/css-modules/blob/master/docs/get-started.md) is building on top of what we said so far. If we don't like the JavaScript syntax then we may use CSS modules and we will be able to write plain CSS. Usually this library plays its role at bundling time. It is possible to hook it as part of the transpilation step but normally is distributed as a build system plugin. +[CSS modules](https://github.com/css-modules/css-modules/blob/master/docs/get-started.md) นั้นสร้างขึ้นจากแนวคิดของสิ่งที่เราได้กล่าวไปก่อนหน้านี้ ถ้าเราไม่ชอบการเขียน CSS ภายใต้ Syntax ของ JavaScript เราสามารถใช้ CSS Module ที่ทำให้เราสามารถเขียน CSS ในรูปแบบและ Syntax ของ CSS ได้ ( ดังที่กล่าวไปว่าการเขียน CSS โดยที่ไม่มี CSS module นั้นจะต้องเขียนใน Syntax ของ JavaScript ) +ปกติแล้ว Library นี้จะจัดการงานของมันในช่วง Building Time มันเป็นไปได้ที่เราจะเข้าใจว่ามันคือส่วนหนึ่งของกระบวนการ [Transpilation](https://scotch.io/tutorials/javaScript-transpilers-what-they-are-why-we-need-them) แต่โดยปกติแล้วมันก็คือ build system plug-in ชนิดหนึ่ง -Here is a quick example to get an idea how it works: - -<br /><br /> +นี่คือตัวอย่างเล็ก ๆ ที่จะช่วยให้คุณเข้าใจว่ามันทำงานอย่างไร: ```js /* style.css */ @@ -62,9 +63,9 @@ function App() { } ``` -That is not possible by default but with CSS modules we may import directly a plain CSS file and use the classes inside. +ตามปกติแล้ววิธีนี้จะไม่สามารถทำได้ แต่ด้วยพลังแห่ง CSS Module เราสามารถ import ไฟล์ CSS และเรียกใช้ Class ที่ประกาศไว้ในไฟล์ CSS Module ได้ -And when we say *plain CSS* we don't mean that it is exactly like the normal CSS. It supports some really helpful composition techniques. For example: +และดังที่เราได้กล่าวไปว่า Plain CSS ใน React นั้น ไม่ได้เหมือนกับ CSS ธรรมดาบน HTML มันทำให้คุณสามารถใช้งานเทคนิคบางอย่างที่เป็นประโยชน์กับคุณได้ ดังตัวอย่าง: ``` .title { @@ -74,7 +75,7 @@ And when we say *plain CSS* we don't mean that it is exactly like the normal CSS ## Styled-components -[Styled-components](https://www.styled-components.com/) took another direction. Instead of inlining styles the library provides a React component. We then use this component to represent a specific look and feel. For example, we may create a `Link` component that has certain styling and use that instead of the `<a>` tag. +[styled-components](https://www.styled-components.com/) นั้นต่างออกไป แทนที่จะเป็น Inline Style ที่เกิดจาก Library เราใช้มันเพื่อทำให้โค้ดของเราดูดีมากขึ้น เช่น เราอาจจะสร้าง Component `Link` ซึ่งมี Style และมีการใช้การใช้งานเหมือนกับ `<a>` ```js const Link = styled.a` @@ -87,7 +88,7 @@ const Link = styled.a` <Link href='http://google.com'>Google</Link> ``` -There is again a mechanism for extending classes. We may still use the `Link` component but change the text color like so: +เช่นเดิมที่เรามีวิธีในการขยายเพิ่ม Class อีกด้วย เราอาจจะยังคงใช้ Component `Link` แต่เปลี่ยนสีตัวอักษรได้ดังนี้: ```js const AnotherLink = styled(Link)` @@ -97,8 +98,8 @@ const AnotherLink = styled(Link)` <AnotherLink href='http://facebook.com'>Facebook</AnotherLink> ``` -By far for me styled-components are probably the most interesting approach for styling in React. It is quite easy to create components for everything and forget about the styling. If your company has the capacity to create a design system and building a product with it then this option is probably the most suitable one. +ตามความคิดเห็นผมแล้ว styled-components เป็นวิธีการที่น่าสนใจที่สุดแล้วในการ Styling ใน React มันง่ายมากในการสร้าง Components สำหรับทุก ๆ อย่างและลดความยุ่งยากในการ Styling ลง ถ้าบริษัทของคุณมีความพร้อมในการทำระบบดีไซน์ ( Design System ) และการพัฒนาระบบด้วย styled-components นี่คงจะเป็นทางเลือกที่ดีที่สุดแล้ว -## Final thoughts +## ข้อคิด -There are multiple ways to style your React application. I did experienced all of them in production and I would say that there is no right or wrong. As most of the stuff in JavaScript today you have to pick the one that fits better in your context. \ No newline at end of file +มีหลายทางเลือกมาก ๆ ในการตกแต่งโปรเจคของคุณ ผมได้ทดลองมามากมายและผมคงจะบอกได้ว่าไม่มีวิธีไหนถูกหรือผิด มีทางเลือกให้คุณได้ใช้มากมาย คุณควรจะเลือกสักวิธีหนึ่งที่ดีที่สุดในโปรเจคของคุณ \ No newline at end of file diff --git a/book/chapter-12/README.md b/book/chapter-12/README.md index c404b7a..3449f46 100644 --- a/book/chapter-12/README.md +++ b/book/chapter-12/README.md @@ -1,10 +1,12 @@ -# Third-party integration +# การใช้งานคู่กับ Third-party Software -React is probably one of the best choices for building UI. Good design, support and community. However, there are cases where we want to use an external service or we want to integrate something completely different. We all know that React works heavily with the actual DOM and basically controls what's rendered on the screen. That's why integrating of third-party components may be tricky. In this section we will see how to mix React and jQuery's UI plugin and do it safely. +React อาจจะเป็นหนึ่งในตัวเลือกที่ดีทีสุดสำหรับการสร้างส่วนประสานกับผู้ใช้ (UI) ตัว React มีการออกแบบโครงสร้างที่ดี รวมถึงมีการสนับสนุน และมีกลุ่ม community ที่ดี อย่างไรก็ตามก็ยังมีในหลายกรณีที่เราต้องการจะใช้ service ภายนอก หรือต้องการที่จะใช้งานกับอะไรสักอย่างที่มันแตกต่างไปอย่างสิ้นเชิง พวกเราทั้งหมดรู้ว่า React ทำงานอย่างหนักกับตัว DOM (Document Object Model) จริง ๆ ของเว็บ และการควบคุมอะไรก็ตามที่จะแสดงผลออกมาทางหน้าจอเป็นพื้นฐาน นั่นก็คือเหตุผลที่การการใช้งานควบคู่กับ third-party software ค่อนข้างที่จะต้องใช้เทคนิคที่อาจจะยุ่งยาก ในส่วนนี้เราจะแสดงการใช้งาน React ควบคู่กับ jQuery's UI plugin และค่อย ๆ ทำมันอย่างปลอดภัย -## The example +## ตัวอย่าง -I picked [*tag-it*](https://github.com/aehlke/tag-it) jQuery plugin for my example. It transforms an unordered list to input field for managing tags: +ผมได้เลือกใช้ [*tag-it*](https://github.com/aehlke/tag-it) ซิ่งเป็นส่วนเสริมตัวนึงของ jQuery มาใช้เป็นตัวอย่างนะครับ มันเอาไว้แปลงแท็ก (tag) ul ที่เอาไว้แสดงผลข้อมูลรายการที่ไม่เป็นลำดับให้กลายเป็นตัวป้อนข้อมูลที่จะไว้ใช้ในการจัดการแท็ก + +จากภาษา HTML ด้านล่างนี้: ```html <ul> @@ -13,19 +15,19 @@ I picked [*tag-it*](https://github.com/aehlke/tag-it) jQuery plugin for my examp </ul> ``` -to: +แสดงผลได้ดังนี้: ![tag-it](./tag-it.png) -To make it work we have to include jQuery, jQuery UI and the *tag-it* plugin code. It works like that: +เพื่อให้มันทำงานได้ เราจำเป็นจะต้องมี jQuery, jQuery UI และที่ขาดไม่ได้คือ plugin *tag-it*; tag-it มันใช้งานประมาณนี้ครับ: ```jsx $('<dom element selector>').tagit(); ``` -We select a DOM element and call `tagit()`. +อธิบายก็คือเราเลือก DOM element และไปเรียกใช้งานฟังก์ชันที่ชื่อ `target()` -Now, let's create a simple React app that will use the plugin: +เอาละครับ มาสร้าง React app ง่าย ๆ ขึ้นมาตัวนึง ที่จะมาลองใช้กับ plugin: ```jsx // Tags.jsx @@ -62,11 +64,11 @@ class App extends React.Component { ReactDOM.render(<App />, document.querySelector('#container')); ``` -The entry point is our `App` class. It uses the `Tags` component that displays an unordered list based on the passed `tags` prop. When React renders the list on the screen we know that we have a `<ul>` tag so we can hook it up to the jQuery plugin. +เข้าไปที่ class ที่ชื่อว่า `App` มันใช้งานตัว component ที่ชื่อว่า `Tags` ที่จะทำการแสดงผลเจ้าตัวรายการที่ไม่เป็นลำดับ (unordered list) ด้วยการส่งค่าผ่าน props ที่ชื่อว่า `tags` แล้วเมื่อ React ทำการแสดงรายการที่ว่าบนหน้าจอ เราจะรู้ว่าเรามีแท็ก `<ul>` เพื่อเราสามารถเชื่อมมันเข้ากับ jQuery plugin -## Force a single-render +## บังคับให้เกิดการ render เพียงแค่ครั้งเดียว (single-render) -The very first thing that we have to do is to force a single-render of the `Tags` component. That is because when React adds the elements in the actual DOM we want to pass the control of them to jQuery. If we skip this both React and jQuery will work on same DOM elements without knowing for each other. To achieve a single-render we have to use the lifecycle method `shouldComponentUpdate` like so: +สิ่งแรกที่เราจะต้องทำคือการบังคับให้เกิดการ render เพียงแค่ครั้งเดียว (single-render) ของ component `Tags` นั่นเพราะเมื่อ React เพิ่ม element ต่าง ๆ เข้าไปที่ DOM จริง ๆ (actual DOM) เราต้องการที่จะส่งการควบคุม element ต่าง ๆ ไปให้ jQuery ถ้าเราข้ามขั้นตอนนี้ไป React และ jQuery จะทำงานอยู่บน DOM ตัวเดียวกัน โดยไม่รู้ซึ่งกันและกัน เพื่อให้เกิดการ render ครั้งเดียว เราจะต้องใช้ method ที่อยู่ใน lifecycle ของ React ที่ชื่อว่า `shouldComponentUpdate` อย่างเช่นโค้ดด้านล่างนี้: ```jsx class Tags extends React.Component { @@ -76,11 +78,12 @@ class Tags extends React.Component { ... ``` -By always returning `false` here we are saying that our component will never re-render. If defined `shouldComponentUpdate` is used by React to understand whether to trigger `render` or not. That is ideal for our case because we want to place the markup on the page using React but we don't want to rely on it after that. -## Initializing the plugin +โดยการส่งค่ากลับมาเป็น `false` เสมออย่างนี้ เรากล่าวว่า component ของเราจะไม่ re-render อีก การใช้งาน method `shouldComponentUpdate` เพื่อให้ React รู้ว่าต้องมีการ `render` ใหม่หรือไม่ นั่นเป็นสิ่งที่เราต้องทำเพราะว่า เราจะใช้ React เพื่อวางโครงสร้างเว็บ แต่หลังจากนั้นเราไม่ต้องการที่จะให้มันมายุ่งเกี่ยวกับการ `render` ใหม่อีก + +## การเตรียมพร้อมสำหรับส่วนขยาย -React gives us an [API](https://facebook.github.io/react/docs/refs-and-the-dom.html) for accessing actual DOM nodes. We have to use the `ref` attribute on a node and later reach that node via `this.refs`. `componentDidMount` is the proper lifecycle method for initializing the *tag-it* plugin. That's because we get it called when React mounts the result of the `render` method. +React มี [API](https://facebook.github.io/react/docs/refs-and-the-dom.html) มาตัวนึงสำหรับการเข้าถึง DOM nodes จริงๆ ที่อยู่ใน HTML (actual DOM nodes) เราจะต้องใช้ attribute ที่ชื่อว่า `ref` กับตัว node และเราจะใช้การเข้าถึงตัว node ผ่าน `this.refs` ซึ่ง `componentDidMount` เป็น lifecycle method ที่เหมาะสำหรับการเรียกใช้ plugin *tag-it* นั่นเป็นเพราะว่ามันจะถูกเรียกเมื่อ React นำผลลัพธ์จาก method render ไป mount ใน DOM nodes จริง ๆ <br /><br /><br /> @@ -105,13 +108,13 @@ class Tags extends React.Component { ... ``` -The code above together with `shouldComponentUpdate` leads to React rendering the `<ul>` with two items and then *tag-it* transforms it to a working tag editing widget. +ตัวโค้ดที่อยู่ด้านบนกับ method `shouldComponentUpdate` ทำให้ React จะ render ตัว `<ul>` ที่มีสองอัน (<ul> และ </ul>) หลังจากนั้นตัว *tag-it* จะแปลงมันให้เป็น widget สำหรับการแก้ไขตัว tag -## Controlling the plugin using React +## การควบคุมส่วนขยายด้วย React -Let's say that we want to programmatically add a new tag to the already running *tag-it* field. Such action will be triggered by the React component and needs to use the jQuery API. We have to find a way to communicate data to `Tags` component but still keep the single-render approach. +สมมติว่าเราต้องการที่จะโปรแกรมเพิ่ม แท็กตัวใหม่ที่กำลังทำงานอยู่แล้วกับ *tag-it* การทำงานดังกล่าวจะถูกเรียกใช้งานโดย React component และต้องการใช้ jQuery API ด้วย; เราต้องหาวิธีที่จะติดต่อ component ที่ชื่อว่า `Tags` ให้มีการ `render` ถ้ามีการแก้ไขข้อมูล แต่ยังคงใช้วิธีการ single-render เหมือนเดิม -To illustrate the whole process we will add an input field to the `App` class and a button which if clicked will pass a string to `Tags` component. +เพื่อแสดงขั้นตอนทั้งหมด เราจะเพิ่มตัวป้อนข้อมูลเข้าไปที่คลาส `App` และปุ่ม ซึ่งถ้าปุ่มถูกคลิกจะส่งตัวอักขระไปให้ component ที่ชื่อ `Tags` <br /> @@ -146,7 +149,7 @@ class App extends React.Component { } ``` -We use the internal state as a data storage for the value of the newly added field. Every time when we click the button we update the state and trigger re-rendering of `Tags` component. However, because of `shouldComponentUpdate` we have no any updates on the screen. The only one change is that we get a new value of the `newTag` prop which may be captured via another lifecycle method - `componentWillReceiveProps`: +เราใช้ state ภายใน class `App` เป็นเหมือนกับที่เก็บข้อมูลสำหรับค่าของตัวที่พึ่งถูกเพิ่มเข้าในในฟิลด์ใหม่ ทุกครั้งที่เราคลิกตัวปุ่ม ตัว React จะทำการอัปเดต state และจะไปเรียกการ re-rendering ของ component `Tags` อย่างไรก็ตามเพราะว่า `shouldComponentUpdate` React จึงไม่มีการอัปเดตใด ๆ บนหน้าจอ สิ่งอย่างเดียวที่เปลี่ยนนั่นคือเมื่อเราได้ค่าใหม่ของ prop ที่ชื่อว่า `newTag` ซึ่งอาจถูกจับมาได้ด้วย lifecycle method ตัวหนึ่งที่ชื่อว่า `componentWillReceiveProps`: <br /><br /><br /> @@ -159,9 +162,9 @@ class Tags extends React.Component { ... ``` -`.tagit('createTag', newProps.newTag)` is a pure jQuery code. `componentWillReceiveProps` is a nice place for calling methods of the third-party library. +`.tagit('createTag', newProps.newTag)` คือโค้ดที่เป็น pure jQuery; `componentWillReceiveProps` คือ lifecycle method ที่เหมาะสมสำหรับการเรียก method ที่มาจาก third-party library -Here is the full code of the `Tags` component: +นี่คือโค้ดที่สมบูรณ์ของ component `Tags`: ```jsx class Tags extends React.Component { @@ -191,6 +194,6 @@ class Tags extends React.Component { <br /> -## Final thoughts +## ข้อคิด -Even though React is manipulating the DOM tree we are able to integrate third-party libraries and services. The available lifecycle methods give us enough control on the rendering process so they are the perfect bridge between React and non-React code. +ถึงแม้ว่า React จะเป็นคนจัดการ DOM tree เราสามารถก็ที่ยังสามารถใช้งานกับ third-party libraries และ services โดยให้ lifecycle method ต่าง ๆ เป็นตัวเชื่อมระหว่าง React กับ non-React code diff --git a/book/chapter-13/README.md b/book/chapter-13/README.md index 00f4597..883df39 100644 --- a/book/chapter-13/README.md +++ b/book/chapter-13/README.md @@ -1,3 +1,3 @@ -# Summary +# บทสรุป -React became on of the most popular libraries for building UI. It comes with great API which is simple and powerful. The tricky part thought is that React itself is not always enough for building complex applications. There are concepts that we have to know to make it right. Design patterns that are introduced by community and work well in scale. This book teaches most of those patterns in a slightly opinioned style. I hope you liked it :) \ No newline at end of file +React ได้กลายเป็น libraries ที่เป็นที่นิยมมากที่สุดสำหรับการสร้างส่วนติดต่อผู้ใช้ ตัว React เองมาพร้อมกับ API (Application Program Interface) ที่ดีเยียม มันทั้งดูเรียบง่ายและมีประสิทธิภาพเป็นอย่างยิ่ง ส่วนที่ยุ่งยากคิดว่ามันคือตัว React เองมันไม่เพียงพอสำหรับการสร้างแอปพลิเคชันที่มีความซับซ้อนได้ในทุก ๆ โอกาส มันมีแนวคิดว่าเราจะต้องรู้ที่จะสร้างมันอย่างถูกต้อง รูปแบบของการออกแบบถูกแนะนำโดย community และทำงานได้ในระดับที่ดี หนังสือเล่มนี้สอนรูปแบบส่วนใหญ่เหล่านั้นในมุมมองที่เล็ก ๆ หวังว่าคุณจะชอบมันนะ 😎 diff --git a/book/chapter-2/README.md b/book/chapter-2/README.md index cb2ef1a..2e7d4b8 100644 --- a/book/chapter-2/README.md +++ b/book/chapter-2/README.md @@ -1,12 +1,12 @@ -# Communication +# การติดต่อสื่อสาร -Every React component is like a small system that operates on its own. It has its own state, input and output. In the following section we will explore these characteristics. +ทุก ๆ React component เป็นเหมือนระบบเล็ก ๆ ที่บริหารจัดการตัวเอง ตัวมันเองจะมีข้อมูลสถานะ (state) เป็นของตัวเอง และมีส่วนของ ข้อมูลนำเข้า (input) และ ข้อมูลส่งออก (output) ซึ่งเป็นคุณลักษณะที่เราจะกล่าวถึงในหัวข้อนี้ ![Input-Output](./communication.jpg) -## Input +## ข้อมูลนำเข้า (Input) -The input of a React component is its props. That's how we pass data to it: +ข้อมูลนำเข้าของ React component คือสิ่งที่เรียกว่า props (Properties หรือข้อมูลคุณลักษณะของมันนั่นเอง) ซึ่งก็คือสิ่งที่กำหนดว่าเราจะเราสามารถส่งข้อมูลอะไรเข้าไปที่ตัว component ได้บ้าง ```js // Title.jsx @@ -26,9 +26,13 @@ function App() { } ``` -The `Title` component has only one input (prop) - `text`. The parent component (`App`) should provide it as an attribute while using the `<Title>` tag. Alongside the component definition we also have to define at least `propTypes`. In there we define the type of every property and React hints us in the console if something unexpected gets send. `defaultProps` is another useful option. We may use it to set a default value of component's props so that if the developer forgets to pass them we have meaningful values. +component ข้างต้นที่มีชื่อว่า `Title` และมีการรับข้อมูลนำเข้าเพียงข้อมูลเดียวคือ `text` ซึ่งควรจะถูกส่งมาจาก parent component (component ที่ห่อหุ้ม Title อีกทีหนึ่ง) -React is not defining strictly what should be passed as a prop. It may be whatever we want. It could even be another component: +ตัวอย่างข้างต้นยังได้มีการระบุ `propTypes` หรือชนิดของข้อมูลนำเข้า ซึ่งเป็นสิ่งที่ควรกำหนดให้ถูกต้องตามแต่ละชนิดของข้อมูลคุณลักษณะ เพื่อที่ React จะสามารถแจ้งเตือนเราได้หากมีการส่งชนิดข้อมูลที่ไม่ตรงกับที่ระบุไว้ + +`defaultProps` ก็เป็นอีกสิ่งหนึ่งที่มีประโยชน์ในการกำหนดค่าเริ่มต้นให้กับข้อมูลนำเข้า ซึ่งเราอาจจะต้องการกำหนดค่าเริ่มต้นที่เราต้องการเผื่อไว้ในกรณีที่ผู้เรียกใช้ component ของเราลืมส่งข้อมูลมาให้ + +React ไม่ได้กำหนดเจาะจงชนิดของข้อมูลนำเข้าของ component เลย มันอาจจะเป็นอะไรก็ได้ เราสามารถส่งแม้แต่ component อื่น ๆ เข้ามาเป็นข้อมูลนำเข้าของ component ของเราอีกทีหนึ่ง ดังเช่นตัวอย่างด้านล่าง: ```js function SomethingElse({ answer }) { @@ -42,7 +46,8 @@ function Answer() { <SomethingElse answer={ <Answer /> } /> ``` -There is also a `props.children` property that gives us access to the child components passed by the owner of the component. For example: +สังเกตุตรง `props.children` ซึ่งถือเป็นข้อมูลนำเข้าอีกชนิดที่ทำให้เราสามารถที่จะเข้าถึง component ลูก (child components) ที่ถูกระบุอยู่ใน component ที่เรียกใช้ component ของเราอีกที +ดังเช่นตัวอย่างด้านล่าง: ```js function Title({ text, children }) { @@ -62,15 +67,15 @@ function App() { } ``` -In this example `<span>community</span>` in `App` component is `children` in `Title` component. Notice that if we don't return `{ children }` as part of the `Title`'s body the `<span>` tag will not be rendered. +ในตัวอย่างข้างต้นนี้ ตรง `<span>community</span>` ที่อยู่ใน `App` component เป็น component ลูก (`children`) ของ component `Title` ท่านจะสังเกตุเห็นได้ว่าถ้าหากเราไม่มีบรรทัด `{ children }` ที่เป็นส่วนนึงของโค้ด `Title` จะทำให้ `<span>community</span>` ไม่ถูกแสดงผล -(prior v16.3) An indirect input to a component may be also the so called `context`. The whole React tree may have a `context` object which is accessible by every component. More about that in the [dependency injection](../chapter-10/README.md) section. +(ก่อนเวอร์ชั่น 16.3) มีข้อมูลนำเข้าทางอ้อมที่ส่งไปให้ component เรียกว่า `context` ซึ่ง component ทั้งหมดที่อยู่ภายใต้ลำดับชั้นของ context นั้น ๆ สามารถที่จะเข้าถึงข้อมูล context นั้นได้ (จะกล่างถึงอย่างละเอียดอีกครั้งในหัวข้อ [dependency injection](../chapter-10/README.md) ) -## Output +## ข้อมูลส่งออก (Output) -The first obvious output of a React component is the rendered HTML. Visually that is what we get. However, because the prop may be everything including a function we could also send out data or trigger a process. +ข้อมูลส่งออกแบบแรกที่ชัดเจนที่สุดของ React component ก็คือ HTML ที่ถูกประมวลผลออกมาแล้ว เป็นสิ่งที่สามารถเห็นได้ง่าย ๆ อย่างไรก็ตาม เพราะว่าเราสามารถที่จะส่งอะไรเข้ามาเป็นข้อมูลนำเข้าก็ได้ เราจึงสามารถที่จะส่งฟังก์ชัน (function) เข้ามาเพื่อที่จะส่งข้อมูลกลับออกไปหรือกระตุ้นให้เกิดการเริ่มกระบวนการที่ต้องการได้ด้วย -In the following example we have a component that accepts the user's input and sends it out (`<NameField />`). +ในตัวอย่างด้านล่างเรามี component ชื่อ `<NameField />` ที่ด้านในของมันเป็น html input tag ทำหน้าที่รับข้อมูลนำเข้าจากผู้ใช้และมี prop (ข้อมูลนำเข้าของตัว NameField เอง) ที่ชื่อว่า `valueUpdated` เป็นเหมือน callback (ฟังก์ชันที่ส่งข้อมูลกลับเมื่อสิ้นสุดการทำงาน) ที่คอยส่งค่าที่ user ป้อนเข้ามา ออกไปยัง component ที่เรียกใช้ตัว `<NameField />` อีกทีนึง (`<App />`) <span class="new-page"></span> @@ -99,7 +104,7 @@ class App extends React.Component { }; ``` -Very often we need an entry point of our logic. React comes with some handy lifecycle methods that may be used to trigger a process. For example we have an external resource that needs to be fetched on a specific page. +บ่อยครั้งที่เราต้องการจุดเริ่มต้นสำหรับ logic ของเรา และ React มาพร้อมกับ lifecycle method ต่าง ๆ ที่เราสามารถใช้ในการระบุการทำงานที่ต้องการในแต่ละช่วงสถานะของตัว component ได้ ดังเช่นตัวอย่างด้านล่างที่เราพยายามจะดึงข้อมูลจากทรัพยากรภายนอก ```js class ResultsPage extends React.Component { @@ -116,8 +121,8 @@ class ResultsPage extends React.Component { } ``` -Let's say that we are building a search-results experience. We have a search page and we enter our criteria there. We click submit and the user goes to `/results` where we have to display the result of the search. Once we land on the results page we render some sort of a loading screen and trigger a request for fetching the results in `componentDidMount` lifecycle hook. When the data comes back we pass it to a `<List>` component. +ลองคิดว่าเรากำลังจะสร้างส่วนของการค้นหา ซึ่งเรามีหน้าสำหรับค้นหาที่รับเงื่อนไขในการค้นหาอยู่แล้ว user อาจจะกรอกเงื่อนไขและกดค้นหา ซึ่งจะนำผู้ใช้ไปอยู่หน้า `/results` ที่ ๆ เราจะแสดงผลของการค้นหาของเรา และเมื่อผู้ใช้เข้าสู่หน้าแสดงผลสำเร็จแล้วเราก็จะให้ผู้ใช้พบกับส่วนที่แสดงว่ากำลังทำการดึงข้อมูลอยู่ให้ผู้ใช้รอ พลางทำการร้องขอข้อมูลไปที่ทรัพยากรด้านนอก ในขั้นตอนนี้เราจะทำใน `componentDidMount` ที่เป็น lifecycle method ของ React component และเมื่อเราได้ผลลัพธ์กลับมาจากแหล่งข้อมูลที่เราร้องขอ เราก็จะนำข้อมูลมาแสดงให้กับผู้ใช้ใน `<List>` component ตามโค้ดตัวอย่างด้านบน -## Final thoughts +## ข้อคิด -It is nice that we may think about every React component as a black box. It has its own input, lifecycle and output. It is up to us to compose these boxes. And maybe that is one of the advantages that React offers. Easy to abstract and easy to compose. +มันอาจจะง่ายขึ้นหากว่าเราคิดว่าทุก ๆ React component เป็นเหมือนกล่องดำ (black box) ที่มันจะมีรูปแบบของข้อมูลนำเข้า ข้อมูลส่งออก และ lifecycle ของมันเอง ขึ้นอยู่กับเราว่าจะประกอบกล่องต่าง ๆ เหล่านี้อย่างไร และบางทีนี่อาจจะเป็นหนึ่งในข้อได้เปรียบที่ React มีให้กับเรา ซึ่งก็คือการที่มันง่ายต่อการนิยามและง่ายต่อการประกอบขึ้นมาให้เป็นสิ่่งที่ใช้ได้จริง ๆ diff --git a/book/chapter-3/README.md b/book/chapter-3/README.md index 7335605..882f820 100644 --- a/book/chapter-3/README.md +++ b/book/chapter-3/README.md @@ -1,17 +1,20 @@ -# Event handlers +# การจัดการ Event -React provides a series of attributes for handling events. The solution is almost the same as the one used in the standard DOM. There are some differences like using camel case or the fact that we pass a function but overall it is pretty similar. +React นั้นได้มีการเตรียม attributes ต่าง ๆ ที่ใช้สำหรับการจัดการกับ event ไว้เรียบร้อยแล้ว ซึ่งวิธีใช้ทั่วไปนั้นแทบจะเหมือนกับวิธีการจัดการ event ใน DOM ที่เราคุ้นเคยเลย โดยจะมีความแตกต่างกันเพียงเล็กน้อย เช่น การใช้ `camelCase` เป็นชื่อ attribute หรือการส่ง function แทนที่จะเป็น string เป็นต้น ```js const theLogoIsClicked = () => alert('Clicked'); +// onClick event <Logo onClick={ theLogoIsClicked } /> + +// onChange event <input type='text' onChange={event => theInputIsChanged(event.target.value) } /> ``` -Usually we handle events in the component that contains the elements dispatching the events. Like in the example below, we have a click handler and we want to run a function or a method of the same component: +ส่วนใหญ่แล้วเรามักจะจัดการ event กันภายในคอมโพเนนท์ที่สร้าง event นั้นขึ้นมา เช่นในตัวอย่างข้างล่าง เรามี `button` อยู่ในคอมโพเนนท์ `Switcher` แล้วเราต้องการให้การคลิกที่ `button` ไปรันคำสั่งชื่อ `_handleButtonClick` ที่อยู่ในคอมโพเนนท์ `Switcher` ```js class Switcher extends React.Component { @@ -28,7 +31,9 @@ class Switcher extends React.Component { }; ``` -That's all fine because `_handleButtonClick` is a function and we indeed pass a function to the `onClick` attribute. The problem is that as it is the code doesn't keep the same context. So, if we have to use `this` inside `_handleButtonClick` to refer the current `Switcher` component we will get an error. +โค้ดชุดนี้จะสามารถทำงานได้ตรงตามที่เราต้องการ เพราะ `_handleButtonClick` นั้นเป็น *function* และเราก็ส่ง *function* เข้าไปใน attribute ชื่อ `onClick` + +**แต่!!** เนื่องจากตัวโค้ดไม่ได้อยู่ใน `context` (บริบท) เดียวกัน ส่งผลให้เวลาที่เราต้องการเรียกถึงตัวแปร `this` ข้างในฟังก์ชัน `_handleButtonClick` เพื่อเรียกถึงคอมโพเนนท์ `Switcher` จะทำให้เกิด Error ขึ้นมาทันที ```js class Switcher extends React.Component { @@ -45,13 +50,13 @@ class Switcher extends React.Component { } _handleButtonClick() { console.log(`Button is clicked inside ${ this.state.name }`); - // leads to + // ไม่สามารถเรียก this.state.name ได้ เพราะหา this ไม่เจอ เนื่องจาก context ของ this ไม่ตรงกัน // Uncaught TypeError: Cannot read property 'state' of null } }; ``` -What we normally do is to use `bind`: +เราสามารถแก้ได้โดยการใช้ `bind` ```js <button onClick={ this._handleButtonClick.bind(this) }> @@ -59,7 +64,7 @@ What we normally do is to use `bind`: </button> ``` -However, this means that the `bind` function is called again and again because we may render the button many times. A better approach would be to create the bindings in the constructor of the component: +เสียแต่ว่าฟังก์ชัน `bind` ของเรานั้นจะถูกเรียกซ้ำไปซ้ำมาอยู่บ่อยๆ เพราะว่าคอมโพเนนท์ `button` อาจถูก render ใหม่หลายๆครั้ง (หรือที่เราเรียกกันว่า re-render) วิธีที่ดีกว่านี้ก็คือการเปลี่ยนไป `bind` ที่ `constructor` ของคอมโพเนนท์นั้นทีเดียวเลย <span class="new-page"></span> @@ -68,6 +73,7 @@ class Switcher extends React.Component { constructor(props) { super(props); this.state = { name: 'React in patterns' }; + // ทำการ binding ที่นี่แทน this._buttonClick = this._handleButtonClick.bind(this); } render() { @@ -83,9 +89,9 @@ class Switcher extends React.Component { }; ``` -Facebook by the way [recommend](https://facebook.github.io/react/docs/reusable-components.html#no-autobinding) the same technique while dealing with functions that need the context of the same component. +Facebook (ผู้สร้าง React) เองก็ยัง [แนะนำ](https://reactjs.org/docs/handling-events.html) เทคนิคเดียวกันนี้เวลาที่ต้องจัดการกับฟังก์ชันที่ใช้ context เดียวกันภายในคอมโพเนนท์ -The constructor is also a nice place for partially executing our handlers. For example, we have a form but want to handle every input in a single function. +Constructor ยังถือเป็นที่ที่ดีสำหรับการสร้าง handler ที่มีค่าบางอย่างพร้อมแล้วอีกด้วย ยกตัวอย่างเช่น เมื่อเรามี `<form>` ที่มีหลาย `<input>` อยู่ข้างใน แต่เราต้องการจัดการการทำงานเมื่อ `<input>` ถูกเปลี่ยนในฟังก์ชัน `_onFieldChange(field, event)` เพียงที่เดียว <span class="new-page"></span> @@ -94,8 +100,7 @@ class Form extends React.Component { constructor(props) { super(props); this._onNameChanged = this._onFieldChange.bind(this, 'name'); - this._onPasswordChanged = - this._onFieldChange.bind(this, 'password'); + this._onPasswordChanged = this._onFieldChange.bind(this, 'password'); } render() { return ( @@ -111,6 +116,6 @@ class Form extends React.Component { }; ``` -## Final thoughts +## ข้อคิด -There is not much to learn about event handling in React. The authors of the library did a good job in keeping what's already there. Since we are using HTML-like syntax it makes total sense that we have also a DOM-like event handling. +การจัดการ event ใน React นั้นอาจดูเหมือนไม่มีอะไรใหม่ให้ศึกษาสักเท่าไหร่ เพราะคนสร้าง React นั้นถือว่าทำไว้ดีแล้วในเรื่องของการนำสิ่งที่มีอยู่แล้วมาใช้ ในเมื่อตัวไลบรารี่เองมีการใช้ syntax ที่เหมือนกับ HTML เดิมอยู่แล้ว จึงไม่ใช่เรื่องแปลกอะไรที่จะมีการจัดการ event เหมือนใน DOM diff --git a/book/chapter-4/README.md b/book/chapter-4/README.md index 170e5d4..b4c23f3 100644 --- a/book/chapter-4/README.md +++ b/book/chapter-4/README.md @@ -1,14 +1,14 @@ # Composition -One of the biggest benefits of React is composability. I personally don't know a framework that offers such an easy way to create and combine components. In this section we will explore few composition techniques which proved to work well. +หนึ่งในความสามารถที่มีประโยชน์มากของ React ก็คือ composability โดยส่วนตัวผมยังไม่รู้จัก framework ไหนที่มีวีธีในการสร้างและรวม component ได้อย่างที่ React ทำได้เลย ซึ่งในบทนี้เราก็จะมาลองดูเทคนิค composition บางตัวที่ได้รับการพิสูจน์ว่าใช้งานได้ดีกันนะครับ -Let's get a simple example. Let's say that we have an application with a header and we want to place a navigation inside. We have three React components - `App`, `Header` and `Navigation`. They have to be nested into each other so we end up with the following dependencies: +เริ่มจากตัวอย่างง่าย ๆ กันเลย สมมติว่าเรามี application ที่มีส่วนของ header อยู่ และเราต้องการที่จะวาง navigation ไว้ข้างใน ในกรณีนี้เรามี React component อยู่สามตัว ได้แก่ `App`, `Header`, และ `Navigation` โดยทั้งสามตัวต้องอยู่ในสภาพซ้อนทับกัน (nested) ซึ่งสามารถแสดง dependency ได้ดังนี้: ```js <App> -> <Header> -> <Navigation> ``` -The trivial approach for combining these components is to reference them in the places where we need them. +วิธีการง่าย ๆ ในการรวม component เหล่านี้ก็คือการอ้างถึง component ในที่ต่าง ๆ ที่เราต้องการจะให้แต่ละ component ไปอยู่ ```js // app.jsx @@ -31,14 +31,14 @@ export default function Navigation() { } ``` -However, by following this approach we introduced couple of problems: +อย่างไรก็ตาม หากเราทำตามวิธีการข้างต้น เราจะเจอกับปัญหาสองสามอย่าง: -* We may consider the `App` as a place where we do our main composition. The `Header` though may have other elements like a logo, search field or a slogan. It will be nice if they are passed somehow from the `App` component so we don't create a hard-coded dependencies. What if we need the same `Header` component but without the `Navigation`. We can't easily achieve that because we have the two bound tightly together. -* It's difficult to test. We may have some business logic in the `Header` and in order to test it we have to create an instance of the component. However, because it imports other components we will probably create instances of those components too and it becomes heavy to test. We may break our `Header` test by doing something wrong in the `Navigation` component which is totally misleading. *(Note: to some extent [shallow rendering](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering) solves this problem by rendering only the `Header` without its nested children.)* +* เราสามารถมองว่า `App` เป็นที่ที่เราจะทำ composition หลักได้ แต่ทั้งนี้ `Header` อาจจะมี element อื่น ๆ อย่างโลโก้ ช่องค้นหา หรือสโลแกนอยู่ด้วย ซึ่งคงดีมากหากด้วยวิธีการบางอย่าง element เหล่านี้สามารถถูกส่งมาจาก `App` component ได้ เพื่อที่เราจะได้ไม่ต้อง hard code ลงไป แล้วในกรณีที่เราต้องการ `Header` component ตัวเดิมแต่ไม่ต้องการ `Navigation` ล่ะ? จะเห็นว่าเราไม่สามารถแก้ปัญหานี้ได้ง่าย ๆ เนื่องจาก component ทั้งสองนั้นผูกกันอยู่ +* การทดสอบจะกลายเป็นเรื่องยาก โดยเราอาจจะมี business logic บางส่วนอยู่ใน `Header` และเพื่อที่จะทดสอบ logic เหล่านั้นเราจำเป็นต้องสร้าง instance ของ component ขึ้นมา อย่างไรก็ตาม เนื่องจาก `Header` ทำการ import component อื่น ๆ เข้ามาด้วย เราจึงอาจจะจำเป็นต้องสร้าง instance ของ component พวกนั้นเช่นกัน ซึ่งจะทำให้การทดสอบกลายเป็นงานที่หนักมากไปเลย นอกจากนี้การทดสอบ `Header` อาจจะล้มเหลวเพราะความผิดพลาดภายใน `Navigation` ก็ได้ ซึ่งจะทำให้เกิดความเข้าใจผิดขึ้นด้วย *(หมายเหตุ: ในบางกรณี [shallow rendering](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering) สามารถแก้ปัญหานี้ได้โดยการ render เฉพาะ `Header` และไม่สนใจลูก ๆ ที่ซ้อนอยู่ข้างใน)* -## Using React's children API +## การใช้ React's children API -In React we have the handy [`children`](https://facebook.github.io/react/docs/multiple-components.html#children) prop. That's how the parent reads/accesses its children. This API will make our Header agnostic and dependency-free: +ใน React นั้นมี prop ที่มีประโยชน์มากเรียกว่า [`children`](https://facebook.github.io/react/docs/multiple-components.html#children) ซึ่ง parent จะใช้ในการอ่านหรือเข้าถึงลูก ๆ โดย API นี้จะทำให้ Header ของเรานั้นกลายเป็น dependency-free ได้ ```js export default function App() { @@ -53,13 +53,13 @@ export default function Header({ children }) { }; ``` -Notice also that if we don't use `{ children }` in `Header`, the `Navigation` component will never be rendered. +สังเกตว่า ถ้าเราไม่ใช้ `{ children }` ใน `Header` จะทำให้ `Navigation` component ไม่ถูก render เลย -It now becomes easier to test because we may render the `Header` with an empty `<div>`. This will isolate the component and will let us focus on one piece of our application. +ซึ่งตอนนี้การทดสอบก็จะง่ายขึ้นมาแล้ว เพราะเราสามารถ render `Header` ด้วย `<div>` เปล่า ๆ ได้ นี่จะทำให้ component อยู่แยกกันและทำให้เราสามารถโฟกัสไปที่แต่ละส่วนของ application ได้ -## Passing a child as a prop +## การส่งลูกในรูปของ prop -Every React component receives props. As we mentioned already there is no any strict rule about what these props are. We may even pass other components. +ทุก ๆ component ใน React สามารถที่จะรับ prop ได้ และอย่างที่เราได้กล่าวไปแล้วว่าไม่มีกฏไหนบอกว่า prop เหล่านี้จะต้องเป็นอะไร เราอาจถึงขั้นส่ง component อื่น ๆ เข้าไปเป็น prop ก็ได้ ```js const Title = function () { @@ -82,13 +82,13 @@ function App() { }; ``` -This technique is useful when a component like `Header` needs to take decisions about its children but don't bother about what they actually are. A simple example is a visibility component that hides its children based on a specific condition. +เทคนิคนี้จะมีประโยชน์เมื่อ component อย่าง `Header` จำเป็นต้องทำการตัดสินใจเกี่ยวกับลูก ๆ แต่ไม่จำเป็นต้องรู้ว่าจริง ๆ แล้วลูกคืออะไร ตัวอย่างง่าย ๆ ก็เช่น visibility component ที่ซ่อนลูกไว้ตามเงื่อนไขบางอย่าง ## Higher-order component -For a long period of time higher-order components were the most popular way to enhance and compose React elements. They look really similar to the [decorator design pattern](http://robdodson.me/javascript-design-patterns-decorator/) because we have component wrapping and enhancing. +เป็นเวลานานมากแล้วที่ higher-order component เป็นวิธีที่นิยมใช้ในการ enhance และประกอบ (compose) React element ซึ่ง pattern ชนิดนี้มีความคล้ายคลึงอย่างมากกับ [decorator design pattern](http://robdodson.me/javascript-design-patterns-decorator/) เพราะมีทั้ง wrap และ enhance เช่นกัน -On the technical side the higher-order component is usually a function that accepts our original component and returns an enhanced/populated version of it. The most trivial example is as follows: +ในทางเทคนิค higher-order component ปกติจะเป็น function ที่รับ component ดั้งเดิมและ return ตัวมันในรูปแบบที่ถูก enhance หรือ populate ตัวอย่างเล็ก ๆ ก็เช่น: ```js var enhanceComponent = (Component) => @@ -110,7 +110,7 @@ class App extends React.Component { }; ``` -The very first thing that the higher-order component does is to render the original component. It's a good practice to proxy pass the `props` to it. This way we will keep the input of our original component. And here it comes the first big benefit of this pattern - because we control the input of the component we may send something that the component usually has no access to. Let's say that we have a configuration setting that `OriginalTitle` needs: +สิ่งแรกที่ higher-order component ทำก็คือ render component ดั้งเดิม โดย good practice คือการทำ proxy pass `props` เข้าไป วิธีนี้จะทำให้เรายังสามารถเก็บ input ของ component ดั้งเดิมไว้ได้ และเนื่องจากเราควบคุม input ของ component อยู่ เราจึงอาจจะส่งอะไรเข้าไปก็ได้ อะไรที่ปกติแล้ว component ไม่สามารถเข้าถึงได้ ตรงส่วนนี้เองที่ถือเป็นประโยชน์ใหญ่ ๆ ข้อแรก ลองสมมติว่าเรามีค่า config ที่ `OriginalTitle` ต้องใช้: ```js var config = require('path/to/configuration'); @@ -131,9 +131,9 @@ var OriginalTitle = ({ title }) => <h1>{ title }</h1>; var EnhancedTitle = enhanceComponent(OriginalTitle); ``` -The knowledge for the `appTitle` is hidden into the higher-order component. `OriginalTitle` knows only that it receives a `prop` called `title`. It has no idea that this is coming from a configuration file. That's a huge advantage because it allows us to isolate blocks. It also helps with the testing of the component because we can create mocks easily. +ค่าของ `appTitle` นั้นถูกซ่อนอยู่ใน higher-order component โดย `OriginalTitle` จะรู้เพียงแค่ว่ามันรับ `prop` ที่เรียกว่า `title` เข้ามาและไม่รู้เลยว่า prop ดังกล่าวมาจากไฟล์ configuration ซึ่งนั่นก็คือข้อได้เปรียบที่ใหญ่มากข้อหนึ่ง เพราะตอนนี้เราสามารถแยก block การทำงานออกจากกันได้และยังช่วยเรื่องการทำการทดสอบ component ด้วย เนื่องจากเราสามารถสร้าง mock ได้อย่างง่ายดายแล้ว -Another characteristic of this pattern is that we have a nice buffer for additional logic. For example, if our `OriginalTitle` needs data also from a remote server. We may query this data in the higher-order component and again send it as a prop. +อีกหนึ่งคุณลักษณะของ pattern นี้ก็คือ เรามี buffer สำหรับใส่ logic เพิ่มเติมเข้าไปได้อีกด้วย ตัวอย่างเช่น ถ้า `OriginalTitle` ต้องใช้ข้อมูลซึ่งมาจาก remote server เราอาจจะทำการ query ข้อมูลนี้ใน higher-order component และส่งกลับไปในรูปของ prop ก็ได้ <span class="new-page"></span> @@ -166,15 +166,15 @@ var OriginalTitle = ({ title, remoteTitle }) => var EnhancedTitle = enhanceComponent(OriginalTitle); ``` -Again, the `OriginalTitle` knows that it receives two props and has to render them next to each other. Its only concern is how the data looks like not where it comes from and how. +เช่นเคย `OriginalTitle` รู้แค่ว่ามันรับ prop เข้ามาสองตัวและต้อง render ต่อกัน สิ่งเดียวที่ `OriginalTitle` ต้องกังวลคือข้อมูลนั้นมีหน้าตาอย่าไร ไม่ใช่ว่ามาจากไหนและมาได้ยังไง -*[Dan Abramov](https://github.com/gaearon) made a really [good point](https://github.com/krasimir/react-in-patterns/issues/12) that the actual creation of the higher-order component (i.e. calling a function like `enhanceComponent`) should happen at a component definition level. Or in other words, it's a bad practice to do it inside another React component because it may be slow and lead to performance issues.* +*[Dan Abramov](https://github.com/gaearon) ได้พูดไว้[อย่างน่าสนใจ](https://github.com/krasimir/react-in-patterns/issues/12)ว่า ขั้นตอนการสร้าง higher-order component (ซึ่งก็คือการเรียก function อย่าง `enhanceComponent`) นั้นควรจะเกิดขึ้นที่ระดับนิยามของ component (component definition level) หรือพูดอีกอย่างก็คือ เป็น bad practice หากจะสร้าง higher-order component ข้างใน React component อีกตัว เพราะจะทำให้ช้าและอาจนำไปสู่ performance issue ได้* <br /><br /> -## Function as a children, render prop +## การใช้ function เป็น children และ render prop -Last couple of months the React community started shifting in an interesting direction. So far in our examples the `children` prop was a React component. There is however a new pattern gaining popularity in which the same `children` prop is a JSX expression. Let's start by passing a simple object. +ในช่วงสองสามเดือนหลังนี้ React community เริ่มที่จะเปลี่ยนทิศทางความสนใจบ้างแล้ว จนถึงตอนนี้ ในตัวอย่างของเรา `children` prop คือ React component ตัวหนึ่ง อย่างไรก็ตามยังมีอีก pattern หนึ่งที่กำลังได้รับความนิยมเพิ่มขึ้น นั่นก็คือ `children` prop ตัวเดิมที่กลายมาอยู่ในรูปของ JSX expression เรามาเริ่มด้วยการลองส่ง object ธรรมดา ๆ กันดูก่อนนะครับ ```js function UserName({ children }) { @@ -197,7 +197,7 @@ function App() { } ``` -This may look weird but in fact is really powerful. Like for example when we have some knowledge in the parent component and don't necessary want to send it down to children. The example below prints a list of TODOs. The `App` component has all the data and knows how to determine whether a TODO is completed or not. The `TodoList` component simply encapsulate the needed HTML markup. +ตัวอย่างนี้อาจจะดูแปลกอยู่ซักหน่อย แต่จริง ๆ แล้วเป็นวิธีที่ใช้งานได้ดีมาก เช่นในกรณีที่เรามี knowledge บางอย่างใน component แม่ และไม่อยากที่จะต้องส่ง knowledge นั้นให้ลูก ตัวอย่างข้างล่างนี้พิมพ์ลิสต์ของ TODO ออกมา โดย `App` component จะเก็บข้อมูลทั้งหมดไว้และรู้วิธีตรวจสอบว่า TODO นี้เสร็จหรือยัง ซึ่ง `TodoList` component นั้นทำหน้าที่แค่ห่อหุ้ม HTML markup ที่จำเป็นไว้เท่านั้น <br /><br /><br /><br /> @@ -233,9 +233,9 @@ function App() { } ``` -Notice how the `App` component doesn't expose the structure of the data. `TodoList` has no idea that there is `label` or `status` properties. +สังเกตวิธีที่ `App` component ไม่ได้เปิดเผยโครงสร้างของข้อมูลเลย และ `TodoList` ก็ไม่รู้ว่ามี `label` หรือ `status` property อยู่ด้วย -The so called *render prop* pattern is almost the same except that we use a prop and not `children` for rendering the todo. +โดย *render prop* pattern นั้นก็เป็นเช่นเดียวกัน ยกเว้นแต่ว่าเราใช้ prop ไม่ใช่ `children` ในการ render todo <br /><br /><br /> @@ -262,7 +262,7 @@ return ( ); ``` -These two patterns, *function as children* and *render prop* are probably one of my favorite ones recently. They provide flexibility and help when we want to reuse code. They are also a powerful way to abstract imperative code. +ซึ่ง pattern ทั้งสองตัว ได้แก่ *function as children* และ *render prop* นั้นเมื่อไม่นานมานี้ได้กลายมาเป็นหนึ่งใน pattern โปรดของผมแล้ว โดยทั้งสองมอบ flexibility และช่วยในกรณีที่เราต้องการ reuse code และทั้งสองยังเป็นวิธีการที่ใช้ได้ดีมากในการ abstract code ส่วนที่สำคัญ ๆ อีกด้วย ```js class DataProvider extends React.Component { @@ -281,13 +281,13 @@ class DataProvider extends React.Component { } ``` -`DataProvider` renders nothing when first gets mounted. Five seconds later we update the state of the component and we render a `<section>` followed by what is `render` prop returning. Imagine that this same component fetches data from a remote server and we want to display it only when it is available. +`DataProvider` ไม่ได้ reder อะไรเลยเมื่อครั้งแรกที่ถูก mount แต่ห้าวินาทีหลังจากที่เราอัพเดท state ของ component เราได้ทำการ render `<section>` ตามด้วยสิ่งที่ prop `render` return กลับมา ลองนึกภาพว่า component ตัวเดียวกันนี้ดึงข้อมูลมาจาก remote server และเราต้องการจะแสดงผลก็ต่อเมื่อข้อมูลดังกล่าวมาถึงแล้วเท่านั้น ```js <DataProvider render={ data => <p>The data is here!</p> } /> ``` -We do say what we want to happen but not how. That is hidden inside the `DataProvider`. These days we used this pattern at work where we had to restrict some UI to certain users having `read:products` permissions. And we used the *render prop* pattern. +เราระบุว่าเราต้องการให้อะไรเกิดขึ้นแต่ไม่ใช่เกิดขึ้นได้อย่างไร วิธีการดังกล่าวถูกซ่อนอยู่ภายใน `DataProvider` โดยในช่วงหลัง ๆ เราใช้ pattern นี้ในงานที่เราต้องจำกัด UI บางส่วนไว้ที่ผู้ใช้บางกลุ่มที่มี permission `read:products` จากนั้นเราจึงใช้ *render prop* pattern ```js <Authorize @@ -295,8 +295,8 @@ We do say what we want to happen but not how. That is hidden inside the `DataPro render={ () => <ProductsList /> } /> ``` -Pretty nice and self-explanatory in a declarative fashion. `Authorize` goes to our identity provider and checks what are the permissions of the current user. If he/she is allowed to read our products we render the `ProductList`. +ผลลัพธ์ที่ออกมานั้นดูสวยและยังอธิบายตัวเองไปในตัวได้อีกด้วย โดย `Authorize` ถูกส่งไปที่ identity provider และตรวจสอบว่า permission ของผู้ใช้ปัจจุบันคืออะไร ถ้าผู้ใช้ได้รับอนุญาติให้อ่าน products ได้ เราก็จะ render `ProductList` -## Final thoughts +## ข้อคิด -Did you wonder why HTML is still here. It was created in the dawn of the internet and we still use it. That is because is highly composable. React and its JSX looks like HTML on steroids and as such it comes with the same capabilities. So, make sure that you master the composition because that is one of the biggest benefits of React. \ No newline at end of file +เคยสงสัยมั้ยว่าทำไม HTML ถึงยังคงมีอยู่จนถึงปัจจุบันนี้ทั้งที่ถูกสร้างในยุกแรกของ Internet แต่เราก็ยังคงใช้กันอยู่ นั่นเป็นเพราะ HTML มี composability ที่สูงมาก ซึ่ง React และ JSX เองก็ดูคล้ายกับ HTML ที่ได้รับ steroid เข้าไป ในรูปแบบที่ว่าทั้งสองมี composibility ที่สูงเช่นเดียวกัน ฉะนั้นจงทำให้แน่ใจว่าคุณได้เรียนรู้เทคนิค composition อย่างลึกซึ้งแล้ว เพราะนั่นคือหนึ่งในความสามรถที่มีประโยชน์ที่สุดของ React diff --git a/book/chapter-5/README.md b/book/chapter-5/README.md index 11eafea..2df8793 100644 --- a/book/chapter-5/README.md +++ b/book/chapter-5/README.md @@ -1,6 +1,8 @@ -# Controlled and uncontrolled inputs +# อินพุตควบคุม (Controlled Input) และ อินพุตอิสระ (Uncontrolled Input) -These two terms *controlled* and *uncontrolled* are very often used in the context of forms management. *controlled* input is an input that gets its value from a single source of truth. For example the `App` component below has a single `<input>` field which is *controlled*: +*อินพุตควบคุม (Controlled Input)* และ *อินพุตอิสระ (Uncontrolled Input)* จะถูกนำไปใช้ในการจัดการข้อมูล หรือ action ต่างๆของ form + +*อินพุตควบคุม (Controlled Input)* นั้นค่าของอินพุตจะถูกกำหนดด้วยข้อมูลจากภายนอก ที่เรามักจะใช้ค่านี้จากแหล่งข้อมูลเดีนว (Single source of truth) ดังตัวอย่างด้านล่าง component `App` มี element `<input>` อยู่หนึ่งตัว ซึ่งเป็น *Controlled Input* ```js class App extends React.Component { @@ -12,10 +14,9 @@ class App extends React.Component { return <input type='text' value={ this.state.value } />; } }; -``` - -The result of this code is an input element that we can focus but can't change. It is never updated because we have a single source of truth - the `App`'s component state. To make the input works as expected we have to add an `onChange` handler and update the state (the single source of truth). Which will trigger a new rendering cycle and we will see what we typed. +``` +ผลลัพธ์ของโค้ดด้านบนจะได้ input ที่เราสามารถกำหนดค่าที่ input นั้นแสดงอยู่ได้ แต่จะไม่สามารถเปลี่ยนแปลงค่าของมันได้เลย เพราะว่าเราได้กำหนดค่าให้อินพุตนั้นโดยนำมาจากค่า state ของ component `App` แต่ถ้าจะให้ input นั้นใช้งานได้อย่างที่ปกติมันควรจะเป็น (คือสามารถกำหนดค่า และ เปลี่ยนแปลงค่าของมันได้) จำเป็นจะต้องเพิ่ม handler ที่เรียกว่า `onChange` เพื่อทำการจัดการและเปลี่ยนค่า state ของ component `App` (ที่ถูกนำไปกำหนดเป็นค่าของอินพุต) ซึ่งทำให้เกิดการ render ใหม่แล้วจึงจะแสดงผลของค่าที่ได้อัพเดทไปแล้วที่ input <span class="new-page"></span> ```js @@ -39,7 +40,7 @@ class App extends React.Component { }; ``` -On the opposite side is the *uncontrolled* input where we let the browser handles the user's updates. We may still provide an initial value by using the `defaultValue` prop but after that the browser is responsible for keeping the state of the input. +ในขณะที่ *อินพุตอิสระ (Uncontrolled Input)* เป็น input ที่ปล่อยให้ browser เป็นตัวจัดการค่าต่างๆที่เกิดขึ้นมาจากการกระทำของ user แต่ถึงอย่างนั้นเราก็ยังสามารถกำหนดค่าเริ่มต้นให้แก่ input ได้โดยการเพิ่ม attribute (prop) ที่เรียกว่า `defaultValue` แล้วหลังจากนั้น browser จะรับหน้าที่เก็บค่าของ input และแสดงผลเอง ```js class App extends React.Component { @@ -53,7 +54,7 @@ class App extends React.Component { }; ``` -That `<input>` element above is a little bit useless because the user updates the value but our component has no idea about that. We then have to use [`Refs`](https://reactjs.org/docs/glossary.html#refs) to get an access to the actual element. +จากตัวอย่างข้างบนนั้น element `<input>` ค่อนข้างจะไร้ประโยชน์ เพราะถ้า user อัพเดทค่าของ input ตัว component `App` นั้นจะไม่รับรู้อะไรเลย จะต้องใช้ตัวอ้างอิง [`Refs`](https://reactjs.org/docs/glossary.html#refs) เพื่อที่จะดึงข้อมูลจาก input โดยตรง ```js class App extends React.Component { @@ -77,12 +78,12 @@ class App extends React.Component { }; ``` -The `ref` prop receives a string or a callback. The code above uses a callback and stores the DOM element into a *local* variable called `input`. Later when the `onChange` handler is fired we get the new value and send it to the `App`'s state. - -*Using a lot of `refs` is not a good idea. If it happens in your app consider using `controlled` inputs and re-think your components.* - -## Final thoughts +การจะใช้ Refs นั้นต้องกำหนด prop ที่ชื่อว่า `ref` และค่าที่กำหนดให้นั้นจะต้องเป็นตัวอักษรสตริง (Legacy String Refs) หรือ callback function* จากตัวอย่าง source code ด้านบนใช้ callback เพื่อที่จะเก็บ DOM element ไว้ที่ตัวแปร *local* ที่มีชื่อว่า `input` และเมื่อ handler `onChange` จับได้ว่า input มีการอัพเดท function ที่มาทำหน้าที่เป็น handler (ในที่นี้คือ `_handleInputChange()`) ก็จะใช้ Refs เพื่ออ้างถึงข้อมูลที่ DOM input นั้นถืออยู่ และนำไปใช้อัพเดทค่า state ของ component `App` -*controlled* versus *uncontrolled* inputs is very often underrated. However I believe that it is a fundamental decision because it dictates the data flow in the React component. I personally think that *uncontrolled* inputs are kind of an anti-pattern and I'm trying to avoid them when possible. +*ปัจจุบัน React สนับสนุนให้ใช้ callback function มากกว่า [Legacy String Refs](https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs) เพราะแบบเก่ายังมี issue และอาจจะถูกนำออกไปในเวอร์ชั่นข้างหน้า +*การใช้ `Refs` บ่อยๆนั้นไม่ใช่ตัวเลือกที่ดีนัก ถ้าเป็นไปได้ควรใช้ หรือ migrate มาใช้ `อินพุตควบคุม` แทน* +## ข้อคิด +คนส่วนใหญ่มักจะมองข้าม ข้อแตกต่างระหว่าง *อินพุตควบคุม* และ *อินพุตอิสระ* แต่โดยพื้นฐานและแนวคิดของ React นั้นจะเป็นการควบคุม data flow เพราะฉะนั้นแนวคิดนี้ค่อนข้างจะสนับสนุนและสอดคล้องกับวิธีและกลไกของ *อินพุตควบคุม* +ส่วนตัวผมนั้นคิดว่าการใช้ *อินพุตอิสระ* ค่อนข้างจะเป็น anti-pattern ถ้าเป็นไปได้ผมมักจะพยายามหลีกเลี่ยงที่จะใช้มัน diff --git a/book/chapter-6/README.md b/book/chapter-6/README.md index 3172597..73aab96 100644 --- a/book/chapter-6/README.md +++ b/book/chapter-6/README.md @@ -1,8 +1,8 @@ -# Presentational and container components +# Presentational component และ Container component -Every beginning is difficult. React is no exception and as beginners we also have lots of questions. Where I'm supposed to put my data, how to communicate changes or how to manage state? The answers of these questions are very often a matter of context and sometimes just practice and experience with the library. However, there is a pattern which is used widely and helps organizing React based applications - splitting the component into presentation and container. +ในการเริ่มต้นทุก ๆ อย่าง มักจะยากเสมอ React เองนั้น ไม่มีการดักจับข้อผิดพลาดและในฐานะผู้เริ่มต้นพวกเราทุกคนต่างก็มีคำถามมากมาย เราจะเอา Data ไปไว้ที่ไหน? การสื่อสารมีการเปลี่ยนแปลงอย่างไร?แล้วะจัดการกับ state ของข้อมูลยังไง คำถามเหล่านี้เป็นเรื่องสำคัญมาก ๆ ทั้งบริบทและหน้าที่แม้ในบางครั้งก็ต้องอาศัยประสบการณ์และการฝึกฝนกับเจ้า React อย่างไรก็ตามยังมีรูปแบบที่ถูกนำมาใช้อย่างแพร่หลายและมันยังช่วยจัดระเบียบพื้นฐานแอพพลิเคชั่นที่พัฒนาจาก React ไปจนถึงการแยก Component ต่าง ๆ ไม่ว่าจะเป็น Presentational component และ Container component -Let's start with a simple example that illustrates the problem and then split the component into container and presentation. We will use a `Clock` component. It accepts a [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object as a prop and displays the time in real time. +เรามาเริ่มต้นกับตัวอย่างง่าย ๆ ซึ่งจะแสดงให้เห็นถึงปัญหา ในการแยก Component ให้อยู่ใน Container และ Presentation `Clock` ซึ่งรับ Object [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) ในรูปแบบของ prop และส่วนที่แสดงผลเวลาในเวลา ณ ตอนนี้ ```js class Clock extends React.Component { @@ -43,21 +43,24 @@ class Clock extends React.Component { ReactDOM.render(<Clock time={ new Date() }/>, ...); ``` +ใน constructor ของ Component นั้นเราได้เริ่มต้น state ของ Component ซึ่งในกรณีนี้เป็นเพียงแค่ค่า `Date` ณ ตอนนี้ โดยใช้ `setInteval` ซึ่งมีการเปลี่ยนแปลง state ทุก ๆ วินาที และตัว Component เองนั้นก็ยังมีการ render ทุกๆครั้งเมื่อ state นั้นเปลี่ยนแปลงค่า เพื่อให้มันดูเหมือนนาฬิกาจริงๆ เราได้ใช้ตัวช่วย 2 method คือ `_formatTime` และ `_UpdateTime` +อันดับแรกคือการ Extract ชั่วโมง นาที และวินาที และเพื่อที่จะแน่ใจกับมันต้องติดตามรูปแบบตัวเลขของมัน +`_updateTime` คือการทำให้ Object `time`เปลี่ยนแปลงค่าให้เป็นปัจุบัน ในทุกๆ 1วินาที + +## ปัญหาที่เกิดขึ้น -In the constructor of the component we initialize the component's state which in our case is just the current `time` value. By using `setInterval` we update the state every second and the component is re-rendered. To make it looks like a real clock we use two helper methods - `_formatTime` and `_updateTime`. The first one is about extracting hours, minutes and seconds and making sure that they are following the two-digits format. `_updateTime` is mutating the current `time` object by one second. +ทั้งสอง Component ของเรา ดูเหมือนจะมีภาระหน้าที่ที่มากเกินไป +* มันอัพเดท state ด้วยตัวมันเอง การเปลี่ยนแปลงเวลาภายใน Component นั้น อาจจะไม่ใช่ความคิดที่ดีเพราะ `Clock` มันจะรู้แค่ค่า Typo เท่านั้น อ้าวแล้วถ้ามีส่วนอื่นของระบบซึ่งข้อมูลมันต้องแบ่งบันซึ่งกันและกัน และมันยากที่แยกมันละ +* `_formatTime` คือการ Extract ข้อมูลที่ต้องใช้งานจาก object `date` และเพื่อให้แน่ใจว่ามันจะมีค่าตัวเลขสองหลักที่จะแสดงผลออกมาเสมอ +อย่างไรก็ตาม เรายังสามารถที่จะ Extract ในส่วนที่เป็นฟังก์ชั่นได้ดี เพราะเมื่อมันสัมพันธ์กับชนิดของ object `time` จาก object `date` มาเป็น prop ซึ่งเป็นที่รู้จักในฐานะของความเฉพาะของข้อมูล และในบางเวลา มันก็เป็นที่รู้จักในนามของการจำลองภาพ -## The problems -There are couple of things happening in our component. It looks like it has too many responsibilities. +## การแยกส่วนย่อยของ Container -* It mutates the state by itself. Changing the time inside the component may not be a good idea because then only `Clock` knows the current value. If there is another part of the system that depends on this data it will be difficult to share it. -* `_formatTime` is actually doing two things - it extracts the needed information from the date object and makes sure that the values are always presented by two digits. That's fine but it will be nice if the extracting is not part of the function because then it is bound to the type of the `time` object (coming as a prop). I.e. knows specifics about the shape of the data and at the same time deals with the visualization of it. +Containers หรือเป็นที่รู้จักในนามของข้อมูลซึ่งมีรูปร่างและที่มาซึ่งหลายคนคงทราบถึงรายละเอียดและการทำงานของมัน หรือจะเรียกอีกอย่างว่า bussiness logic ซึ่งมันได้รับ รูปแบบและข้อมูลที่ดูเหมือนง่ายโดยการใช้ Presentational component, เราได้ใช้ [higher-order components] (https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components) ในการสร้าง container บ่อยมากเพราะมันให้พื้นที่ buffer ซึ่งเราจะสามารถเพิ่มการจัดการข้อมูลด้วยตัวเองได้ -## Extracting the container -Containers know about data, its shape and where it comes from. They know details about how the things work or the so called *business logic*. They receive information and format it so like is easy to be used by the presentational component. Very often we use [higher-order components](https://github.com/krasimir/react-in-patterns/tree/master/patterns/higher-order-components) to create containers because they provide a buffer space where we can insert custom logic. - -Here's how our `ClockContainer` looks like: +นี่คือ `ClockContainer` ลองดูที่ <span class="new-page"></span> @@ -94,14 +97,14 @@ export default class ClockContainer extends React.Component { } }; ``` +จะเห็นได้ว่ามันยังคงรับ `Date` (object) รายละเอียดของข้อมูลที่เป็นเวลา (`getHours`, `getMinutes` และ `getSeconds`) จาก ลูป `setInterval` ในตอนจบของการ render ใน Component presentational นั้น จะส่งผ่านตัวเลข 3 ตัวก็คือ ชั่วโมง นาที และวินาที มันดูเหมือนจะไม่มีมีอะไรเกิดขึ้นในการมองหาเพียงแค่ `bussiness logic`เท่านั้น -It still accepts `time` (a date object), does the `setInterval` loop and knows details about the data (`getHours`, `getMinutes` and `getSeconds`). In the end renders the presentational component and passes three numbers for hours, minutes and seconds. There is nothing about how the things look like. Only *business logic*. -## Presentational component -Presentational components are concerned with how the things look. They have the additional markup needed for making the page pretty. Such components are not bound to anything and have no dependencies. Very often implemented as a [stateless functional components](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components) they don't have internal state. +## Presentational component +Presentational Component เป็นสิ่งที่ดูน่าสนใจ มันยังต้องการการปรับแต่งเพิ่มเติมในการทำให้หน้าต่าง ๆ ดูสวยงามยกตัวอย่าง Component ต่าง ๆ ที่ไม่ได้มีความสัมพันธ์กับสิ่งใด ๆ ก็ตามและยังไม่ได้เกี่ยวข้องกับ dependencies ใด ๆ บ่อยครั้งมากที่ต้องมีการดำเนินการกับมัน ในฐานนะ [stateless functional components](https://facebook.github.io/react/blog/2015/10/07/react-v0.14.html#stateless-functional-components) ซึ่งมันไม่ได้มี state ภายในของมันเอง -In our case the presentational component contains only the two-digits check and returns the `<h1>` tag: +ในกรณีของเรา มีแค่ Presentational component เท่านั้น ซึ่งมีการตรวจสอบตัวเลขสองหลัก ละ return ออกมา ใน tag `<h1>` ```js // Clock/Clock.jsx @@ -116,14 +119,16 @@ export default function Clock(props) { }; ``` -## Benefits - -Splitting the components in containers and presentation increases the reusability of the components. Our `Clock` function/component may exist in application that doesn't change the time or it's not working with JavaScript's [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) object. That's because it is pretty *dummy*. No details about the data are required. - -The containers encapsulate logic and we may use them together with different renderers because they don't leak information about the visual part. The approach that we took above is a good example of how the container doesn't care about how the things look like. We may easily switch from digital to analog clock and the only one change will be to replace the `<Clock>` component in the `render` method. -Even the testing becomes easier because the components have less responsibilities. Containers are not concern with UI. Presentational components are pure renderers and it is enough to run expectations on the resulted markup. +## ประโยชน์ที่ได้รับ +การแยก component ต่าง ๆ นั้น ทั้งใน container component และ presentation component และนำ Component มาใช้อีกครั้งบ่อย ๆ นั้น +จากการยกตัวอย่างของเรา `Clock` คือฟังก์ชั่นหรือ Component ในเวลาเดียวกัน อาจจะมีมีอยู่ใน application ซึ่งมันไม่เปลี่ยนแปลงเวลา หรือไม่ทำงานด้วย Oject [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) ใน Javascirpt เพราะว่ามันคือ *dummy* ที่สวยงาม และไม่มีรายละเอียดเกี่ยวกับข้อมูลที่จำเป็น + +Container ต่าง ๆ ที่ encapsulate logic และเราอาจจะใช้มันร่วมกันซึ่งมันยากต่อการ render ออกมาเพราะข้อมูลมันไม่มีจุดรั่วเกี่ยวกับส่วนจำลองการที่นำมายกตัวอย่างที่ดีของการใช้ container โดยไม่สนใจว่ามันจะมีลักษณะต่าง ๆ ยังไงเราหวังว่ามันจะเปลี่ยนจากนาฬิกาดิติตอลไปเป็นนาฬิกาอนาล็อกได้ง่ายเพียงเท่านั้นมันยังจะถูกที่แทนด้วย Component `Clock` ใน method `render` -## Final thoughts +แม้แต่การทดสอบยังสามารถที่ทำได้ง่ายขึ้น Component ต่าง ๆ ยังมีหน้าที่ที่น้อยลง หรือไม่มีเลย อีกทั้งยังไม่จำเป็นต้องกังวลกับเรื่อง UI +Presentational component ต่าง ๆ นั้นมีการ render สิ่งต่าง ๆ ออกมาได้อย่างดิบ ๆ และ ยังเราเดาถึงผลของการออกแบบหน้าตาได้ -The concept of container and presentation is not new at all but it fits really nicely with React. It makes our applications better structured, easy to manage and scale. +## ข้อคิด +ข้อคิดของ container และ presentation นั้นไม่ใช่เรื่องใหม่ แต่ทุกอย่างเป็นเรื่องที่เหมาะกับ React จริง ๆ ซึ่งมันสามารถสร้างโครงสร้าง +Application ของเราให้ดีขึ้นีกทั้งยังมาสามารถจัดการและปรับปรุงขอบเขตได้ง่าย diff --git a/book/chapter-7/README.md b/book/chapter-7/README.md index 11ff0ca..0e5e22f 100644 --- a/book/chapter-7/README.md +++ b/book/chapter-7/README.md @@ -1,8 +1,8 @@ -# One-way direction data flow +# การไหลข้อมูลแบบทิศทางเดียว -One-way direction data flow is a pattern that works nicely with React. It is around the idea that the components do not modify the data that they receive. They only listen for changes of this data and maybe provide the new value but they do not update the actual data. This update happens following another mechanism in another place and the component just gets re-rendered with the new value. +การไหลข้อมูลแบบทิศทางเดียว (One-way direction data flow) เป็นรูปแบบการจัดการข้อมูลที่ทำงานได้ดีกับ React ไอเดียหลักๆ คือ Component จะไม่แก้ไขข้อมูลใดๆที่ได้รับมา ซึ่ง Component จะคอยดูการเปลี่ยนแปลงของข้อมูลเท่านั้น โดยอาจสร้างข้อมูลใหม่ แต่จะไม่อัพเดทข้อมูลที่ได้รับมา การเปลี่ยนแปลงของข้อมูล จะเกิดขึ้นตามกลไกจากอีกที่หนึ่ง และ Component จะเป็นเพียงตัว render ด้วยข้อมูลชุดใหม่เท่านั้น -Let's for example get a simple `Switcher` component that contains a button. If we click it we have to enable a flag in the system. +มาดูตัวอย่างง่ายๆของ Component `Switcher` กันเถอะ ซึ่งมันประกอบไปด้วยปุ่ม 1 ปุ่ม โดยถ้าคลิกปุ่มเราจะสามารถเปิดค่า flag บางอย่างในระบบได้ ```js class Switcher extends React.Component { @@ -22,13 +22,14 @@ class Switcher extends React.Component { } }; -// ... and we render it +// ... render component ที่นี่ function App() { return <Switcher />; }; ``` -At this moment we have the data inside our component. Or in other words, `Switcher` is the only one place that knows about our `flag`. Let's send it out to some kind of a store: +ในตอนนี้เรามีข้อมูลภายใน Component ของเราแล้ว หรือพูดอีกอย่างหนึ่งได้ว่า ใน Component `Switcher` เป็นที่ๆเดียวที่รู้เกี่ยวกับค่า `flag` ของเรา ดังนั้นมาลองส่งมันออกไปยังตัวเก็บข้อมูล (`Store`) กันเถอะ + ```js var Store = { @@ -65,11 +66,11 @@ function App() { }; ``` -Our `Store` object is a [singleton](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) where we have helpers for setting and getting the value of the `_flag` property. By passing the setter to the `Switcher` we are able to update the data externally. More or less our application workflow looks like that: +object `Store` ของเราเป็น [Singleton](https://addyosmani.com/resources/essentialjsdesignpatterns/book/#singletonpatternjavascript) object ที่ๆเราสามารถมีตัวช่วยสำหรับการเขียนและอ่านข้อมูลของ property `_flag` การส่งตัวเขียนข้อมูลไปยัง `Switcher` จะทำให้เราสามารถอัพเดทข้อมูลจากภายนอกได้ ดังนั้นภาพรวมของระบบการทำงานของแอพพลิเคชั่นจะมีลักษณะไม่ต่างไปจากนี้ -![one-direction data flow](./one-direction-1.jpg) +![การไหลข้อมูลแบบทิศทางเดียว](./one-direction-1.jpg) -Let's assume that we are saving the flag value to a back-end service via the `Store`. When the user comes back we have to set a proper initial state. If the user left the flag as `true` we have to show *"lights on"* and not the default *"lights off"*. Now it gets tricky because we have the data in two places. The UI and the `Store` have their own states. We have to communicate in both directions from the store to the switcher and from the switcher to the store. +สมมติว่าเรากำลังบันทึกค่าของ flag ไปยังเซอร์วิสของระบบหลังบ้านผ่านทาง `Store` เมื่อผู้ใช้กลับมาใช้งานอีกครั้ง เราจะต้องบันทึกสถานะเริ่มต้น (Initial state) ให้ถูกต้อง ถ้าผู้ใช้ตั้งค่า flag เป็น `true` เราต้องแสดง *"lights on"* แทนที่จะเป็นค่าเริ่มต้น *"lights off"* ตอนนี้จะเริ่มยุ่งยากขึ้นแล้ว เพราะว่าเราต้องมีข้อมูลในสองที่ด้วยกันคือ ส่วนของการแสดงข้อมูล (UI) และส่วนของ `Store` ซึ่งแต่ละส่วนนั้นเก็บสถานะ (state) ของตัวเอง เราจึงต้องสื่อสารในสองทิศทางจาก store ไปยัง switcher และจาก switcher กลับไปยัง store ```js // ... in App component @@ -84,13 +85,13 @@ constructor(props) { ... ``` -Our workflow changes to the following: +รูปแบบการทำงานของเราจะเปลี่ยนไปเป็นลักษณะตามภาพดังนี้ -![one-direction data flow](./one-direction-2.jpg) +![การไหลข้อมูลแบบทิศทางเดียว](./one-direction-2.jpg) -All this leads to managing two states instead of one. What if the `Store` changes its value based on other actions in the system. We have to propagate that change to the `Switcher` and we increase the complexity of our app. +จากรูปแบบของการทำงานทั้งหมดนี้ทำให้เราต้องจัดการสถานะสองที่แทนที่จะจัดการภายในที่เดียว ดังนั้นจะเกิดอะไรขึ้นถ้าค่าใน `Store` มีการเปลี่ยนแปลงโดยขึ้นอยู่กับการกระทำอื่นๆในระบบ เราต้องส่งการเปลี่ยนแปลงนั้นกลับมาที่ `Switcher` และเราก็เพิ่มความซับซ้อนของแอพพลิเคชั่น -One-way direction data flow solves this problem. It eliminates the multiple places where we manage states and deals with only one which is usually the store. To achieve that we have to tweak our `Store` object a little bit. We need logic that allows us to subscribe for changes: +การไหลข้อมูลแบบทิศทางเดียวสามารถแก้ปัญหานี้ได้ โดยหลักการคือจะกำจัดส่วนที่จัดการ state หลายๆ จุด ให้เหลือเพียงแค่ที่เดียว ซึ่งปกติแล้วที่ๆนั้นก็คือ store เพื่อที่จะทำแบบนั้นได้เราต้องปรับปรุง object `Store` เล็กน้อย และเราจำเป็นต้องมีลอจิกที่สามารถให้เราติดตามการเปลี่ยนแปลงได้ด้วย <span class="new-page"></span> @@ -111,7 +112,7 @@ var Store = { }; ``` -Then we will hook our main `App` component and we'll re-render it every time when the `Store` changes its value: +จากนั้นเราจะให้ Component `App` render ใหม่ทุกๆครั้งที่ `Store` มีการเปลี่ยนแปลงค่าของมัน ```js class App extends React.Component { @@ -133,7 +134,7 @@ class App extends React.Component { }; ``` -Because of this change the `Switcher` becomes really simple. We don't need the internal state and the component may be written as a stateless function. +เนื่องจากการเปลี่ยนแปลงนี้ การทำงานใน `Switcher` จะง่ายมาก เราไม่จำเป็นต้องมีสถานะภายใน และ Component นี้อาจเขียนได้ในรูปแบบของ function ที่เรียกกันว่า stateless function ได้อีกด้วย ```js function Switcher({ value, onChange }) { @@ -149,6 +150,6 @@ function Switcher({ value, onChange }) { onChange={ Store.set.bind(Store) } /> ``` -## Final thoughts +## ข้อคิด -The benefit that comes with this pattern is that our components become dummy representation of the store's data. There is only one source of truth and this makes the development easier. If you are going to take one thing from this book I would prefer to be this chapter. The one-direction data flow drastically changed the way of how I think when designing a feature so I believe it will have the same effect on you. +ประโยชน์จากรูปแบบนี้คือ Component ของเราจะเป็นเพียง Component ง่ายๆที่ทำหน้าที่แสดงข้อมูลใน store เนื่องจากแหล่งเก็บข้อมูลมีเพียงแค่ที่เดียวเท่านั้น (single source of truth) และนี่จะทำให้การพัฒนาง่ายมากยิ่งขึ้น ถ้าคุณกำลังจะได้สิ่งๆหนึ่งจากหนังสือเล่มนี้ สำหรับฉันแล้วก็จะเป็นเนื้อหาของบทนี้ การไหลข้อมูลแบบทิศทางเดียวจะเปลี่ยนวิธีการคิดของการออกแบบฟีเจอร์อย่างมาก ดังนั้นฉันเชื่อว่ามันจะมีผลเดียวกันนี้กับคุณเช่นกัน diff --git a/book/chapter-8/README.md b/book/chapter-8/README.md index c440e65..9777520 100644 --- a/book/chapter-8/README.md +++ b/book/chapter-8/README.md @@ -1,28 +1,29 @@ # Flux -I'm obsessed by making my code simpler. I didn't say *smaller* because having less code doesn't mean that is simple and easy to work with. I believe that big part of the problems in the software industry come from the unnecessary complexity. Complexity which is a result of our own abstractions. You know, we (the programmers) like to abstract. We like placing things in black boxes and hope that these boxes work together. +ผมมักจะเขียนโค้ดทำให้มันดูง่าย ผมไม่ได้หมายความว่าเขียนโค้ดให้น้อย เพราะการเขียนโค้ดให้น้อยมันก็ไม่ได้หมายความว่ามันจะทำงานง่าย ผมเชื่อว่าปัญหาที่ใหญ่ในวงการของการพัฒนาซอฟต์แวร์นั้นมาจากความซับซ้อนที่ไม่จำเป็น ความซับซ้อนนี้เป็นผลมาจากงานของของเราเองซึ่งมันเป็นสิ่งที่เป็นนามธรรม เหมือนกับการที่เราอะไรใส่อะไรบางอย่างในกล่องดำ (black box) และเราก็คาดหวังว่ามันจะทำงานร่วมกัน -[Flux](http://facebook.github.io/flux/) is an architectural design pattern for building user interfaces. It was introduced by Facebook at their [F8](https://youtu.be/nYkdrAPrdcw?t=568) conference. Since then, lots of companies adopted the idea and it seems like a good pattern for building front-end apps. Flux is very often used with [React](http://facebook.github.io/react/). Another library released by Facebook. I myself use React+Flux/Redux in my [daily job](http://antidote.me/) and I could say that it is simple and really flexible. The pattern helps creating apps faster and at the same time keeps the code well organized. +[Flux](http://facebook.github.io/flux/) เป็นรูปแบบหนึ่งของการออกแบบสถาปัตยกรรมสำหรับการสร้างส่วนติดต่อผู้ใช้ ถูกเผยแพร่โดย Facebook ในงานสัมนา [F8](https://youtu.be/nYkdrAPrdcw?t=568) หลังจากนั้นหลายบริษัทได้นำไปใช้และดูเหมือนว่ามันวิธีการที่ดีในการพัฒนา Front-end apps รูปแบบ Flux ถูกนำมาใช้ควบคู่กับ React บ่อยมาก ตัวผมเองได้ใช้ React+Flux/Redux ในงานประจำของผม และผมบอกได้เลยว่ามันง่ายและยืดหยุ่นจริงๆ รูปแบบดังกล่าวช่วยให้สร้างแอปได้เร็วขึ้นและในเวลาเดียวกัน ก็ช่วยให้โค้ดดูเป็นระเบียบเรียบร้อยมากขึ้น -## Flux architecture and its main characteristics +## สถาปัตยกรรม Flux และลักษณะสำคัญ ![Basic flux architecture](./fluxiny_basic_flux_architecture.jpg) -The main actor in this pattern is the *dispatcher*. It acts as a hub for all the events in the system. Its job is to receive notifications that we call *actions* and pass them to all the *stores*. The store decides if it is interested or not and reacts by changing its internal state/data. That change is triggering re-rendering of the *views* which are (in our case) React components. If we have to compare Flux to the well known MVC we may say that the store is similar to the model. It keeps the data and its mutations. +พระเอกของ Flux คือ *dispatcher* คอยทำหน้าที่เป็นจุดเชื่อมกันสำหรับ event ทั้งหมดของระบบ หน้าที่ของมันคือรอรับการแจ้งเตือนเมื่อเราได้เรียก *action* และส่งไปยัง *store* เพื่อทำการตรสจสอบว่าจะต้องทำการเปลี่ยนแปลงของ state หรือไม่ เมื่อมีการเปลี่ยนแปลงเกิดขึ้นก็ทำการ render *view* (React components) ใหม่ ถ้าเราเปรียบเทียบ Flux กับ MVC อาจจะพูดได้ว่า store เปรียบเสมือนกับ model ที่คอยเก็บข้อมูลและวิธีการในการเปลี่ยนแปลงข้อมูล -The actions are coming to the dispatcher either from the views or from other parts of the system, like services. For example a module that performs a HTTP request. When it receives the result it may fire an action saying that the request was successful. +Action ที่มายัง dispatcher นั้นมาจากทั้งส่วนของ view และส่วนอื่น ๆ ของระบบ เปรียบเสมือนกับเป็นบริการ (service) ยกตัวอย่างเช่นโมดูลที่ทำการร้องขอ HTTP เมื่อได้รับการตอบกลับมาจะทำการดำเนินการบางอย่าง เพื่อทำการบอกว่าการร้องขอนั้นได้สำเร็จแล้ว -## Implementing a Flux architecture -As every other popular concept Flux also has some [variations](https://medium.com/social-tables-tech/we-compared-13-top-flux-implementations-you-won-t-believe-who-came-out-on-top-1063db32fe73). Very often to understand something we have to implement it. In the next few sections we will create a library that provides helpers for building the Flux pattern. +## การใช้งานสถาปัตยกรรม Flux -### The dispatcher +เช่นเดียวกับแนวคิดยอดนิยมอื่น ๆ Flux ก็มีรูปแบบการนำไปใช้ที่[หลากหลาย](https://medium.com/social-tables-tech/we-compared-13-top-flux-implementations-you-won-t-believe-who-came-out-on-top-1063db32fe73) โดยทั่วไปวิธีที่ดีที่สุดในการทำความเข้าใจกับเทคโนโลยีคือการนำไปปฏิบัติ ในส่วนต่อไปนี้เราจะสร้างไลบรารีที่มีฟังก์ชัน helper เพื่อสร้างรูปแบบ Flux -In most of the cases we need a single dispatcher. Because it acts as a glue for the rest of the parts it makes sense that we have only one. The dispatcher needs to know about two things - actions and stores. The actions are simply forwarded to the stores so we don't necessary have to keep them. The stores however should be tracked inside the dispatcher so we can loop through them: +### Dispatcher + +ในหลายกรณีเราจำเป็นต้องมี dispatcher อันเดียว เพราะมันทำหน้าที่เป็นตัวเชื่อมกันกับตัวอื่น ๆ ที่เหลือให้เหมือนกับว่าเพียงแค่อันเดียว dispatcher จะรู้จักกับสองสิ่งนี้คือ action และ store ตัว actions นั้นถูกส่งแค่ไปหา stores เฉยๆ ทำให้เราไม่จำเป็นต้องเก็บมันไว้ ส่วน store สามารถถูก track ได้ภายใน dispatcher เพื่อสามารถดำเนินการกับข้อมูลได้: ![the dispatcher](./fluxiny_the_dispatcher.jpg) -That's what I started with: +ผมจะเริ่มต้นด้วย: ```js var Dispatcher = function () { @@ -42,7 +43,7 @@ var Dispatcher = function () { }; ``` -The first thing that we notice is that we *expect* to see an `update` method in the passed stores. It will be nice to throw an error if such method is not there: +สิ่งแรกที่ควรทราบก็คือเราคาดว่า method update จะมีอยู่ใน store ที่ได้รับมา หากไม่มีควรจะทำการส่ง error กลับไป: ```js register: function (store) { @@ -56,28 +57,29 @@ register: function (store) { <br /> -### Bounding the views and the stores +### ผูก view กับ store -The next logical step is to connect our views to the stores so we re-render when the state in the stores is changed. +ขั้นตอนต่อไปคือการเชื่อมต่อส่วนผู้ใช้กับ store เพื่อให้เราสามารถแสดงผลใหม่เมื่อสถานะของ store มีการเปลี่ยนแปลง ![Bounding the views and the stores](./fluxiny_store_change_view.jpg) -#### Using a helper +#### การใช้งาน helper -Some of the flux implementations available out there provide a helper function that does the job. For example: +การใช้งาน Flux ในบางรูปแบบจะมีฟังก์ชัน helper ในการทำงาน ตัวอย่างเช่น ```js Framework.attachToStore(view, store); ``` - -However, I don't quite like this approach. To make `attachStore` works we expect to see a specific API in the view and in the store. We kind of strictly define new public methods. Or in other words we say "Your views and store should have such APIs so we are able to wire them together". If we go down this road then we will probably define our own base classes which could be extended so we don't bother the developer with Flux details. Then we say "All your classes should extend our classes". This doesn't sound good either because the developer may decide to switch to another Flux provider and has to amend everything. +อย่างไรก็ตามผมไม่ชอบวิธีนี้มากนัก เพื่อให้ `attachToStore` ทำงานได้อย่างถูกต้องเราจำเป็นต้องใช้ API แบบพิเศษใน view และ store ดังนั้นเราจำเป็นต้องกำหนด public method ใหม่อย่างเข้มงวด หรือพูดอีกอย่างหนึ่งคือ view และ store ของคุณควรมี API ดังกล่าวเพื่อให้สามารถเชื่อมต่อกันได้ +ถ้าเราใช้วิธีนี้เราอาจจะกำหนดคลาสหลักที่เป็นส่วยขยายเพื่อที่จะไม่สร้างความสับสนกับรายละเอียดของ Flux ให้กับผู้พัฒนา กลายเป็นว่าทุกคลาสควรจะ extend มาจากคลาสของเรา +มันไม่ใช่ความคิดที่ดีเพราะผู้พัฒนาอาจตัดสินใจเปลี่ยนไปใช้ Flux รูปแบบอื่น และต้องการแก้ไขทุกอย่าง <br /><br /> -#### With a mixin +#### การใช้งาน mixin -What if we use React's [mixins](https://facebook.github.io/react/docs/reusable-components.html#mixins). +เกิดอะไรขึ้นถ้าเราใช้ [mixins](https://facebook.github.io/react/docs/reusable-components.html#mixins) ```js var View = React.createClass({ @@ -85,22 +87,21 @@ var View = React.createClass({ ... }); ``` +นี่เป็นวิธีที่ดีในการกำหนดลักษณะการทำงานสำหรับ component ดังนั้นในทางทฤษฎีเราอาจสร้าง mixin มาผูกกับ component ของเรา ถ้าพูดตามตรงผมไม่คิดว่านี่เป็นความคิดที่ดี และ[ดูเหมือนว่าไม่ใช่แค่ผมที่คิดแบบนี้](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) เหตุผลที่ผมไม่ชอบ mixin คือมันปรับเปลี่ยน components โดยวิธีที่ไม่สามารถคาดเดาได้ ผมไม่รู้ว่าเกิดอะไรขึ้นเบื้องหลังการทำงานของมัน ดังนั้นผมจึงข้ามวิธีการนี้ -That's a "nice" way to define behavior of existing React component. So, in theory we may create a mixin that does the bounding for us. To be honest, I don't think that this is a good idea. And [it looks](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750) like it's not only me. My reason of not liking mixins is that they modify the components in a non-predictable way. I have no idea what is going on behind the scenes. So I'm crossing this option. - -#### Using a context +#### การใช้งาน context -Another technique that may answer the question is React's [context](https://facebook.github.io/react/docs/context.html). It is a way to pass props to child components without the need to specify them in every level of the tree. Facebook suggests context in the cases where we have data that has to reach deeply nested components. +อีกเทคนิคหนึ่งที่อาจตอบโจทย์ได้คือ [context](https://facebook.github.io/react/docs/context.html) เป็นวิธีที่จะ pass props ไปยัง component ย่อยโดยไม่จำเป็นต้องส่งลงไปทีละขั้น Facebook แนะนำให้ใช้ context ในกรณีที่เราส่งข้อมูลไปยัง component ย่อยที่ถูกซ้อนกันหลายชั้น -> Occasionally, you want to pass data through the component tree without having to pass the props down manually at every level. React's "context" feature lets you do this. +> บางครั้ง คุณต้องการส่งข้อมูลไปยัง component โดยไม่ต้อง pass props ในแต่ละระดับด้วยตนเอง context จะช่วยให้คุณทำแบบนั้นได้ง่ายขึ้น -I see similarity with the mixins here. The context is defined somewhere at the top and magically serves props for all the children below. It's not immediately clear where the data comes from. +ผมเห็นความคล้ายคลึงกันกับ mixins ตัว context ถูกกำหนดไว้ที่ไหนสักที่ด้านบน component และคอยส่งข้อมูลไปยัง component ย่อยทุกตัวอย่างน่าอัศจรรย์ โดยที่ไม่รู้ว่าข้อมูลมาจากที่ไหน <br /><br /><br /> -#### Higher-Order components concept +#### แนวคิด Higher-Order components -Higher-Order components pattern is [introduced](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775) by Sebastian Markbåge and it's about creating a wrapper component that returns ours. While doing it it has the opportunity to send properties or apply additional logic. For example: +รูปแบบ Higher-Order components ถูก[เสนอ](https://gist.github.com/sebmarkbage/ef0bf1f338a7182b6775)โดย Sebastian Markbåge จะจะเกี่ยวกับการสร้าง wrapper component ที่จะรีเทิร์น component ออกมา โดยที่สามารถเพิ่มคุณสมบัติให้มันการเดินการบางอย่างเข้าไปด้วย ตัวอย่างเช่น: ```js function attachToStore(Component, store, consumer) { @@ -127,7 +128,7 @@ function attachToStore(Component, store, consumer) { }; ``` -`Component` is the view that we want attached to the `store`. The `consumer` function says what part of the store's state should be fetched and send to the view. A simple usage of the above function could be: +`Component` คือ view ที่เราต้องการจะแนบกับ `store` ฟังก์ชั่น `consumer` จะระบุว่าควรเก็บอะไรใน store และส่งไปที่ view การใช้ฟังก์ชันข้างต้นอย่างง่ายเป็นดังนี้: ```js class MyView extends React.Component { @@ -140,13 +141,14 @@ ProfilePage = connectToStores(MyView, store, (props, store) => ({ ``` -That is an interesting pattern because it shifts the responsibilities. It is the view fetching data from the store and not the store pushing something to the view. This of course has its own pros and cons. It is nice because it makes the store dummy. A store that only mutates the data and says "Hey, my state is changed". It is not responsible for sending anything to anyone. The downside of this approach is maybe the fact that we have one more component (the wrapper) involved. We also need the three things - view, store and consumer to be in one place so we can establish the connection. +นี่เป็นรูปแบบที่น่าสนใจ view จะดึงข้อมูลจาก store ไม่ใช่ให้ store กำหนดว่าจะส่งอะไรออกไป และแน่นอนว่ามันมีทั้งข้อดีและข้อเสีย +ข้อดีคือ ทำให้ง่ายต่อการจัดเก็บ ซึ่ง store จะทำหน้่าที่เพียงแค่เปลี่ยนแปลงข้อมูลและคอยบอกว่าข้อมูลได้ถูกแก้ไขแล้ว มันไม่ได้มีหน้าที่รับผิดชอบต่อการส่งข้อมูลอีกต่อไป ข้อเสียของวิธีการนี้คือ อาจเป็นไปได้ว่าเราจะมี component (wrapper) หลาย ๆ อันที่เกี่ยวข้อง นอกจากนี้เรายังต้องมีสามสิ่งนี้คือ view, store และ consumer วางไว้ในที่เดียวกัน เพื่อให้เแต่ละส่วนสามารถเชื่อมต่อกันได้ -#### My choice +#### ตัวเลือกของผม -The last option above, higher-order components, is really close to what I'm searching for. I like the fact that the view decides what it needs. That *knowledge* anyway exists in the component so it makes sense to keep it there. That's also why the functions that generate higher-order components are usually kept in the same file as the view. What if we can use similar approach but not passing the store at all. Or in other words, a function that accepts only the consumer. And that function is called every time when there is a change in the store. +ผมเลือกวิธีสุดท้าย higher-order components มีความใกล้เคียงกับสิ่งที่ผมต้องการแล้ว ให้ view กำหนดสิ่งที่ต้องการ ความเข้าใจที่มีอยู่เกี่ยวกับคอมโพเนนต์นั้นเหมาะสมเก็บไว้ที่นั่น นั่นคือเหตุผลว่าฟังก์ชั่นที่สร้าง higher-order components จะถูกเก็บไว้ในไฟล์เดียวกับ view ถ้าเราใช้วิธีการแบบเดียวกันแต่ไม่ส่ง store ไป หรือกล่าวอีกนัยหนึ่งฟังก์ชั่นจะรับเฉพาะ consumer เท่านั้น และฟังก์ชันนี้จะถูกเรียกทุกครั้งเมื่อ store มีการเปลี่ยนแปลง -So far our implementation interacts with the store only in the `register` method. +ถึงตอนนี้การใช้งานของเรามีเพียงการโต้ตอบกับ store เท่านั้นใน method `register` ```js register: function (store) { @@ -158,13 +160,13 @@ register: function (store) { } ``` -By using `register` we keep a reference to the store inside the dispatcher. However, `register` returns nothing. And instead of nothing it may return a **subscriber** that will accept our consumer functions. +โดยใช้ `register` เราจะอ้างอิงไปยัง store ภายใน dispatcher อย่างไรก็ตาม `register` จะไม่มีการส่งข้อมูลอะไรกลับไป หรือเราอาจจะให้มันส่ง **subscriber** ที่รับฟังก์ชั่น consumer กลับไป ![Fluxiny - connect store and view](./fluxiny_store_view.jpg) -I decided to send the whole store to the consumer function and not the data that the store keeps. Like in the higher-order components pattern the view should say what it needs by using store's getters. This makes the store really simple and there is no trace of presentational logic. +ผมเลือกส่งทั้ง store ไปยังฟังก์ชัน consumer แทนที่จะส่งแค่ข้อมูลที่เก็บไว้นนั้น เช่นเดียวกันในรูปแบบ higher-order components ส่วน view ควรใช้ getter ของ store เพื่อเรียกสิ่งที่ต้องการ ทำให้ใช้ store ค่อนข้างง่ายและไม่มีร่องรอยของ presentational logic -Here is how the register method looks like after the changes: +นี่คือ method register ที่เปลี่ยนแปลง: ```js register: function (store) { @@ -185,9 +187,9 @@ register: function (store) { } ``` -The last bit in the story is how the store says that its internal state is changed. It's nice that we collect the consumer functions but right now there is no code that execute them. +สิ่งสุดท้ายที่จะทำให้เสร็จสมบูรณ์คือ ทำให้ store แจ้งเมื่อข้อมูลมีการเปลี่ยนแปลง เราได้รวบรวมฟังก์ชัน consumer ไว้แล้ว แต่ตอนนี้ยังไม่มีการใช้งาน -According to the basic principles of the flux architecture the stores change their state in response of actions. In the `update` method we send the `action` but we could also send a function `change`. Calling that function should trigger the consumers: +ตามหลักการพื้นฐานของสถาปัตยกรรม Flux นั้น store จะเปลี่ยนสถานะเพื่อตอบสนองต่อ actions ใน method `update` เราจะส่ง `action` แต่เรายังสามารถส่งฟังก์ชัน `change` การเรียกฟังก์ชันนี้เพื่อให้เกิดการทำงานขึ้นที่ consumer: ```js register: function (store) { @@ -220,9 +222,9 @@ dispatch: function (action) { } ``` -*Notice how we push `change` together with `store` inside the `_stores` array. Later in the `dispatch` method we call `update` by passing the `action` and the `change` function.* +*ถ้าหากว่าเรา push `change` กับ `store` ใน array `_stores` หลังจากนั้นให้ `dispatch` เรียก method `update` และส่ง `action` กับ `change` ไปในฟังก์ชัน* -A common use case is to render the view with the initial state of the store. In the context of our implementation this means firing all the consumers at least once when they land in the library. This could be easily done in the `subscribe` method: +การใช้งานทั่วไป เพื่อสร้าง view และกำหนดสถานะเริ่มต้นของ store ในบริบทของการใช้งานของเราหมายถึง consumer จะถูกเรียกใช้อย่างน้อยหนึ่งครั้งเมื่อเรียกใช้งาน library ซึ่งสามารถทำได้อย่างง่ายใน method `subscribe`: ```js var subscribe = function (consumer, noInit) { @@ -231,7 +233,7 @@ var subscribe = function (consumer, noInit) { }; ``` -Of course sometimes this is not needed so we added a flag which is by default falsy. Here is the final version of our dispatcher: +แน่นอนว่าบางครั้งเราไม่จำเป็นที่จะต้องกำหนดตัวแปร flag ขึ้นมาให้เป็นค่า false และนี่ก็เป็นหน้าตา dispatcher ที่เสร็จแล้ว: <span class="new-page"></span> @@ -274,9 +276,9 @@ var Dispatcher = function () { <span class="new-page"></span> -## The actions +## Actions -You probably noticed that we didn't talk about the actions. What are they? The convention is that they should be simple objects having two properties - `type` and `payload`: +คุณอาจจะเห็นว่าเรายังไม่ได้พูดถึง action แล้ว actions คืออะไร? โดยทั่วไปมันเป็นแค่ object ที่มีเพียง `type` และ `payload`: ```js { @@ -288,9 +290,9 @@ You probably noticed that we didn't talk about the actions. What are they? The c } ``` -The `type` says what exactly the action is and the `payload` contains the information associated with the event. And in some cases we may leave the `payload` empty. +`type` จะเป็นตัวบอกว่า action นั้นทำอะไร ส่วน payload จะเก็บข้อมูลที่เกี่ยวกับ event บางกรณีไม่จำเป็นต้องมีก็ได้ -It's interesting that the `type` is well known in the beginning. We know what type of actions should be floating in our app, who is dispatching them and which of the stores are interested. Thus, we can apply [partial application](http://krasimirtsonev.com/blog/article/a-story-about-currying-bind) and avoid passing the action object here and there. For example: +สิ่งที่น่าสนใจคือ `type` ที่ได้กล่าวไปแล้วในตอนต้น และที่สำคัญคือเราควรจะรู้ว่าต้องมี action อะไรในแอปพลิเคชันของเราบ้าง และตัวไหนที่คอยทำการ dispatch ไปยัง store ดังนั้นเราจึงสามารถประยุกต์ใช้[บางส่วน](http://krasimirtsonev.com/blog/article/a-story-about-currying-bind)ได้ และหลีกเลี่ยงการส่ง object action ตัวอย่างเช่น ```js var createAction = function (type) { @@ -307,19 +309,19 @@ var createAction = function (type) { } ``` -`createAction` leads to the following benefits: +`createAction` มีข้อดีดังนี้: -* We no more need to remember the exact type of the action. We now have a function which we call passing only the payload. -* We no more need an access to the dispatcher which is a huge benefit. Otherwise, think about how we have to pass it to every single place where we need to dispatch an action. -* In the end we don't have to deal with objects but with functions which is much nicer. The objects are *static* while the functions describe a *process*. +* เราไม่จำเป็นต้องจำ type ของ action อีกต่อไป ตอนนี้เรามีฟังก์ชันที่เราเรียกผ่าน payload เท่านั้น +* เราไม่จำเป็นต้องเข้าถึง dispatcher อีกต่อไปซึ่งเป็นประโยชน์อย่างมาก มิเช่นนั้นคุณต้องพิจารณาวิธีส่งข้อมูลทุกครั้งที่ใช้งาน dispatch +* ข้อสุดท้ายเราไม่ต้องจัดการ object อีกต่อไป เราแค่เรียกฟังก์ชันซึ่งดีกว่ามาก ซึ่งเป็น *static* objects ขณะที่ฟังก์ชันอธิบายถึง *process* ![Fluxiny actions creators](./fluxiny_action_creator.jpg) -This approach for creating actions is actually really popular and functions like the one above are usually called *action creators*. +วิธีการสร้าง actions ด้วยวิธีนี้นี้เป็นที่นิยมมาก ฟังก์ชั่นด้านบนเรียกว่า *action creators*. -## The final code +## โค้ดที่สมบูรณ์ -In the section above we successfully hide the dispatcher while submitting actions. We may do it again for the store's registration: +ในส่วนก่อนหน้า dispatcher ถูกซ่อนอยู่ในขณะที่เราดำเนินการ action เราอาจดำเนินการอีกครั้งเพื่อทำการ registry store: ```js var createSubscriber = function (store) { @@ -327,7 +329,7 @@ var createSubscriber = function (store) { } ``` -And instead of exporting the dispatcher we may export only these two functions `createAction` and `createSubscriber`. Here is how the final code looks like: +และแทนที่จะ export dispatcher เราอาจ export เฉพาะสองฟังก์ชันนี้คือ `createAction` และ `createSubscriber` นี่คือโค้ดที่สมบูรณ์ ```js var Dispatcher = function () { @@ -391,17 +393,17 @@ module.exports = { ``` -If we add the support of AMD, CommonJS and global usage we end up with 66 lines of code, 1.7KB plain or 795 bytes after minification JavaScript. +ถ้าเราเพิ่มการสนับสนุน AMD, CommonJS และการใช้งาน global โค้ดที่สมบูรณ์จะมีทั้งหมด 66 บรรทัด ขนาดไฟล์ 1.7KB และถ้าบีบอัดเหลือ 795 bytes โดยการ minifying JavaScript -## Wrapping up +## Wrapper -We have a module that provides two helpers for building a Flux project. Let's write a simple counter app that doesn't involve React so we see the pattern in action. +เรามีโมดูล helper สองอันสำหรับการสร้าง Flux เราจะลองเขียนแอปนับจำนวนแบบง่าย ๆ โดยที่ไม่ใช้ React เพื่อให้เราเห็นรูปแบบการทำงาน <span class="new-page"></span> -### The markup +### Markup -We'll need some UI to interact with it so: +เราจำเป็นต้องมี UI เพื่อโต้ตอบกับข้อมูลดังกล่าว: ```html <div id="counter"> @@ -411,9 +413,9 @@ We'll need some UI to interact with it so: </div> ``` -The `span` will be used for displaying the current value of our counter. The buttons will change that value. +`span` ใช้เพื่อแสดงค่าจำนวนปัจจุบัน เมื่อมีการคลิกปุ่มจะเปลี่ยนจำนวนค่า -### The view +### View ```js const View = function (subscribeToStore, increase, decrease) { @@ -433,23 +435,22 @@ const View = function (subscribeToStore, increase, decrease) { }; ``` -It accepts a store subscriber function and two action function for increasing and decreasing the value. The first few lines of the view are just fetching the DOM elements. +view จะได้รับฟังก์ชั่น store subscriber และ action สำหรับการ เพิ่มค่า / ลดค่า ในบรรทัดแรก ๆ นั้นเป็นเพียงแค่การ fetch DOM elements -After that we define a `render` function which puts the value inside the `span` tag. `updateState` will be called every time when the store changes. So, we pass these two functions to `subscribeToStore` because we want to get the view updated and we want to get an initial rendering. Remember how our consumers are called at least once by default. +หลังจากนั้นเราได้กำหนดฟังก์ชัน `render` ซึ่งมีหน้าที่ในการแสดงค่าลงในแท็ก span และฟังก์ชัน `updateState` ซึ่งจะถูกเรียกใช้เมื่อ store มีการเปลี่ยนแปลง เราได้ส่งสองฟังก์ชั่นนั้นไปใน `subscribeToStore` เนื่องจากเราต้องการให้ view มีการเปลี่ยนแปลงข้อมูล จำไว้ว่าฟังก์ชั่น consumer จะถูกเรียกอย่างน้อยหนึ่งครั้ง -The last bit is calling the action functions when we press the buttons. +สิ่งสุดท้ายที่ต้องทำคือผูก action กับ button element -### The store - -Every action has a type. It's a good practice to create constants for these types so we don't deal with raw strings. +### Store +ทุก action จะมี type การประกาศให้เป็นตัวแปรค่าคงที่วิธีที่ดีที่สุด เนื่องจากเราไม่ต้องมีการประมวลผลกับมัน ```js const INCREASE = 'INCREASE'; const DECREASE = 'DECREASE'; ``` -Very often we have only one instance of every store. For the sake of simplicity we'll create ours as a singleton. +โดยปกติเรามักจะมีเพียงหนึ่ง store เท่านั้น เพื่อความง่ายเรา store ให้มีเพียงอันเดียว ```js const CounterStore = { @@ -468,11 +469,10 @@ const CounterStore = { }; ``` -`_data` is the internal state of the store. `update` is the well known method that our dispatcher calls. We process the action inside and say `change()` when we are done. `getValue` is a public method used by the view so it reaches the needed info. In our case this is just the value of the counter. - -### Wiring all the pieces +`_data` คือ state ที่อยู่ใน store ส่วน `update` นั้นอย่างที่เราได้ทราบกันไปแล้วว่าจะถูกเรียกโดย dispatcher และเราจะ process action ภายในนั้น และฟังก์ชัน `change()` จะถูกเรียกเมื่อข้อมูลได้มีการเปลี่ยนแปลง ส่วน`getValue` นั้นเป็น public method จะถูกเรียกโดย view เมื่อต้องการเรียกข้อมูล ในกรณีนี้เป็นเพียงแค่ข้อมูลจำนวนนับ -So, we have the store waiting for actions from the dispatcher. We have the view defined. Let's create the store subscriber, the actions and run everything. +### เชื่อมทุกส่วนเข้าด้วยกัน +ตอนนี้เรามี store ที่รอ action จาก the dispatcher และได้สร้าง view ไว้แล้ว เหลือเพียงสร้าง store subscriber มาเริ่มทำให้มันทำงานได้กันเถอะ ```js const { createAction, createSubscriber } = Fluxiny.create(); @@ -485,10 +485,11 @@ const actions = { View(counterStoreSubscriber, actions.increase, actions.decrease); ``` -And that's it. Our view is subscribed to the store and it renders by default because one of our consumers is actually the `render` method. +และตอนนี้ view ได้ subscribe store เรียบร้อยแล้ว และจะ render โดย default เพราะ method `render` เป็นหนึ่งใน consumer -### A live demo +### Live demo -A live demo could be seen in the following JSBin [http://jsbin.com/koxidu/embed?js,output](http://jsbin.com/koxidu/embed?js,output). If that's not enough and it seems too simple for you please checkout the example in Fluxiny repository [https://github.com/krasimir/fluxiny/tree/master/example](https://github.com/krasimir/fluxiny/tree/master/example). It uses React as a view layer. +สามารถดูตัวอย่าง live demo ที่เว็บ JSBin [http://jsbin.com/koxidu/embed?js,output](http://jsbin.com/koxidu/embed?js,output) +ถ้าคุณคิดว่าตัวอย่างนี้ยังไม่เพียงพอสามารถไปดูตัวอย่างเพิ่มเติมใน repository Fluxiny [https://github.com/krasimir/fluxiny/tree/master/example](https://github.com/krasimir/fluxiny/tree/master/example) ซึ่งได้ใช้ React ในการสร้าง view -*The Flux implementation discussed in this section is available here [github.com/krasimir/fluxiny](https://github.com/krasimir/fluxiny). Feel free to use it in a browser [directly](https://github.com/krasimir/fluxiny/tree/master/lib) or as a [npm dependency](https://www.npmjs.com/package/fluxiny).* \ No newline at end of file +*การใช้ Flux ที่กล่าวถึงในบทนี้จะอยู่ที่นี่ [github.com/krasimir/fluxiny](https://github.com/krasimir/fluxiny) สามารถใช้งานได้[โดยตรง](https://github.com/krasimir/fluxiny/tree/master/lib)ในเบราว์เซอร์ หรือผ่าน [npm dependency](https://www.npmjs.com/package/fluxiny)* diff --git a/book/chapter-9/README.md b/book/chapter-9/README.md index 79c5773..c8bc861 100644 --- a/book/chapter-9/README.md +++ b/book/chapter-9/README.md @@ -1,20 +1,20 @@ # Redux -[Redux](https://redux.js.org/) is a library that acts as a state container and helps managing your application data flow. It was introduced back in 2015 at ReactEurope conference ([video](https://www.youtube.com/watch?v=xsSnOQynTHs)) by [Dan Abramov](https://twitter.com/dan_abramov). It is similar to [Flux architecture](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-8/README.md#flux-architecture-and-its-main-characteristics) and has a lot in common with it. In this section we will create a small counter app using Redux alongside React. +[Redux](https://redux.js.org/) เป็น Library ที่ทำตัวเป็นตัวเก็บ state และช่วยจัดการ data ภายใน application เปิดตัวครั้งแรกในงาน ReactEurope conference ([วิดีโอ](https://www.youtube.com/watch?v=xsSnOQynTHs)) ในปี 2015 โดย [Dan Abramov](https://twitter.com/dan_abramov) มีลักษณะการทำงานและการออกแบบคล้าย [สถาปัตยกรรม Flux](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-8/README.md#flux-architecture-and-its-main-characteristics) <span class="new-page"></span> -## Redux architecture and its main characteristics +## สถาปัตยกรรม Redux และลักษณะสำคัญ ![Redux architecture](./redux-architecture.jpg) -Similarly to [Flux](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-8/README.md) architecture we have the view components (React) dispatching an action. Same action may be dispatched by another part of our system. Like a bootstrap logic for example. This action is dispatched not to a central hub but directly to the store. We are saying "store" not "stores" because there is only one in Redux. That is one of the big differences between Flux and Redux. The logic that decided how our data changes lives in pure functions called reducers. Once the store receives an action it asks the reducers about the new version of the state by sending the current state and the given action. Then in immutable fashion the reducer needs to return the new state. The store continues from there and updates its internal state. As a final step, the wired to the store React component gets re-rendered. +เช่นเดียวกับ [Flux](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-8/README.md) Redux มี view components (React) ที่คอย dispatch action โดยที่ action เดียวกันสามารถถูก dispatch มาจากส่วนไหนของระบบก็ได้ ยกตัวอย่างเช่น การเรียก bootstrap เป็นต้น action ที่ถูก dispatch จะถูกส่งตรงไปยัง store ซึ่งมีแค่ตัวเดียวเท่านั้นใน Redux ซึ่งสิ่งที่ Redux ไม่เหมือนกับ Flux คือส่วนที่จะตัดสินใจว่า data ของเราจะเปลี่ยนไปอย่างไรนั้นขึ้นอยู่กับ reducers ที่เป็น pure functions เมื่อ store ได้รับ action reducers จะทำการรับ current state และ action ที่ถูกส่งเข้ามา เพื่อคำนวณและสร้าง state ถัดไป โดยอิงหลัก immutable store จะรับช่วงต่อและเปลี่ยนค่า state ภายใน store สุดท้าย React component ที่ดึง data มาจาก store ก็จะถูก re-render -The concept is pretty linear and again follows the [one-direction data flow](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-7/README.md). Let's talk about all these pieces and introduce a couple of new terms that support the work of the Redux pattern. +Concept ของ Redux ค่อนข้างตรงไปตรงมาโดยยึดหลัก [one-direction data flow](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-7/README.md) เรามาเริ่มจาก แนะนำส่วนประกอบต่าง ๆ และตัวช่วยใน Redux pattern กันดีกว่า ### Actions -The typical action in Redux (same as Flux) is just an object with a `type` property. Everything else in that object is considered a context specific data and it is not related to the pattern but to your application logic. For example: +โดยทั่วไปแล้ว action ใน Redux เป็นเพียง object ที่ประกอบไปด้วย property ที่ชื่อว่า `type` ส่วน property อื่น ๆ ใน object คือ data ที่เกี่ยวข้องกับบริบทของ action นั้น ๆ และไม่เกี่ยวข้องกับ pattern ของ Redux แต่อย่างใด ยกตัวอย่างเช่น ```js const CHANGE_VISIBILITY = 'CHANGE_VISIBILITY'; @@ -23,12 +23,11 @@ const action = { visible: false } ``` +ถือว่าเป็นแบบอย่างที่ดีที่เราสร้าง constant อย่าง `CHANGE_VISIBILITY` เป็น action type ซึ่งยังมี tools หรือ libraries หลาย ๆ อย่างที่รองรับ Redux ที่มีวีธีเรียกใช้ที่สะดวกโดยการส่ง action type อย่างเดียว -It is a good practice that we create constants like `CHANGE_VISIBILITY` for our action types. It happens that there are lots of tools/libraries that support Redux and solve different problems which do require the type of the action only. So it is just a convenient way to transfer this information. +ส่วนของ property `visible` เป็น metadata ที่เราได้กล่าวถึง ซึ่งไม่ได้ถูกใช้ใน Redux เป็นเพียงแค่ข้อมูลที่ใช้ใน application เท่านั้น -The `visible` property is the meta data that we mentioned above. It has nothing to do with Redux. It means something in the context of the application. - -Every time when we want to dispatch a method we have to use such objects. However, it becomes too noisy to write them over and over again. That is why there is the concept of *action creators*. An action creator is a function that returns an object and may or may not accept an argument which directly relates to the action properties. For example the action creator for the above action looks like this: +ทุก ๆ ครั้งที่เราต้องการ dispatch method เราต้องใช้ object ซึ่งมันเป็นการยุ่งยากถ้าจะต้องมาเขียนมันซ้ำแล้วซ้ำเล่า จึงเป็นที่มาของ *action creators* ที่เป็น function ที่ return ค่า object และอาจจะรับหรือไม่รับ argument ที่เกี่ยวข้องกับ action นั้นเพิ่มก็ได้ ยกตัวอย่างเช่น action creator ของ action ด้านบน จะมีหน้าตาตามด้านล่าง ```js const changeVisibility = visible => ({ @@ -40,11 +39,11 @@ changeVisibility(false); // { type: CHANGE_VISIBILITY, visible: false } ``` -Notice that we pass the value of the `visible` as an argument and we don't have to remember (or import) the exact type of the action. Using such helpers makes the code compact and easy to read. +จะสังเกตได้ว่าเราทำการส่งค่าของ `visible` ผ่าน argument ทำให้เราไม่ต้องจดจำค่าจริงของ action type นั้น ซึ่งการใช้ตัวช่วยพวกนี้จะทำให้โค้ดของเราสั้นและง่ายต่อการอ่าน ### Store -Redux provides a helper `createStore` for creating a store. Its signature is as follows: +Redux ได้เตรียมตัวช่วยอย่าง `createStore` ไว้สำหรับการสร้าง store โดย function มีลักษณะดังนี้ ```js import { createStore } from 'redux'; @@ -52,27 +51,27 @@ import { createStore } from 'redux'; createStore([reducer], [initial state], [enhancer]); ``` -We already mentioned that the reducer is a function that accepts the current state and action and returns the new state. More about that in a bit. The second argument is the initial state of the store. This comes as a handy instrument to initialize our application with data that we already have. This feature is the essence of processes like server-side rendering or persistent experience. The third parameter, enhancer, provides an API for extending Redux with third party middlewares and basically plug some functionally which is not baked-in. Like for example an instrument for handling async processes. +เราได้กล่าวถึงไว้แล้วว่า reducer เป็น function ที่รับ current state และ action และ return ค่าเป็น state ใหม่ ต่อมา argument ที่สองคือ state เริ่มต้น ซึ่งมีประโยชน์ในการกำหนดค่า state เมื่อ application เริ่มทำงาน ซึ่ง feature นี้เป็นส่วนสำคัญของกระบวนการทำ server-side rendering หรือ persistent experience ส่วน argument ที่สามคือ enhancer ไว้ใช้สำหรับเชื่อมต่อกับ third party API หรือ function ที่ไม่ได้มีใน Redux ยกตัวอย่างเช่น function ที่ไว้จัดการ async processes -Once created the store has four methods - `getState`, `dispatch`, `subscribe` and `replaceReducer`. Probably the most important one is `dispatch`: +store ที่สร้างขึ้นมาประกอบไปด้วย 4 method คือ `getState`, `dispatch`, `subscribe` และ `replaceReducer` ซึ่งตัวที่สำคัญที่สุดคือ `dispatch` ```js store.dispatch(changeVisibility(false)); ``` -That is the place where we use our action creators. We pass the result of them or in other words action objects to this `dispatch` method. It then gets spread to the reducers in our application. +ด้านบนเป็นวิธีที่เราเรียกใช้ action creators โดยเราจะส่งผลลัพธ์ที่ได้จาก action creators ซึ่งก็คือ action object ไปยัง `dispatch` method ซึ่งจะถูกกระจายต่อไปยัง reducer แต่ละตัวที่อยู่ใน application -In the typical React application we usually don't use `getState` and `subscribe` directly because there is a helper (we will see it in the next sections) that wires our components with the store and effectively `subscribe`s for changes. As part of this subscription we also receive the current state so we don't have to call `getState` ourself. `replaceReducer` is kind of an advanced API and it swaps the reducer currently used by the store. I personally never used this method. +โดยทั่วไปแล้วใน React application เราจะไม่ค่อยได้ใช้ `getState` และ `subscribe` ตรง ๆ เพราะเรามีตัวช่วย (เราจะอธิบายในส่วนต่อไป) ในการที่จะเชื่อมต่อ component ของเราเข้ากับ store และ `subscribe` อย่างมีประสิทธิภาพเมื่อมีการเปลี่ยนแปลง ในส่วนของ subscription เรายังได้รับ current state ทำให้ไม่ต้องเรียก `getState` เองอีกด้วย ส่วน `replaceReducer` เป็น API ที่ค่อนข้างซับซ้อน ใช้สำหรับเปลี่ยน reducer ที่กำลังถูก store ใช้งานอยู่ โดยส่วนตัวผมแล้ว ผมยังไม่มีโอกาสได้ใช้เลย ### Reducer -The reducer function is probably the most *beautiful* part of Redux. Even before that I was interested in writing pure functions with an immutability in mind but Redux forced me to do it. There are two characteristics of the reducer that are quite important and without them we basically have a broken pattern. +reducer เป็น function ที่เรียกได้ว่า *สวยงามที่สุด* ภายใน Redux แม้ก่อนหน้านี้ ผมจะเริ่มสนใจในการเขียน pure fuction ที่มีคุณสมบัติ immutability อยู่แล้ว แต่ Redux บังคับให้ผมต้องเขียน reducer มีคุณสมบัติที่สำคัญอยู่ 2 ข้อด้วยกัน หากขาดหายไปแล้ว pattern นี้ก็จะไม่สมบูรณ์ -(1) It must be a pure function - it means that the function should return the exact same output evert time when the same input is given. +(1) ต้องเป็น pure function เท่านั้น หมายความว่า function ควรจะ return ค่าเดียวกันทุกครั้งหากมี input ที่เหมือนกัน -(2) It should have no side effects - stuff like accessing a global variable, making an async call or waiting for a promise to resolve have no place in here. +(2) ควรจะไม่มี side effects กล่าวคือไม่ควรมีการแก้ไขค่า global variable, การเรียก async function หรือ การใช้งาน promise -Here is a simple counter reducer: +นี่คือตัวอย่างง่าย ๆ ของ counter reducer ```js const counterReducer = function (state, action) { @@ -85,13 +84,13 @@ const counterReducer = function (state, action) { }; ``` -There are no side effects and we return a brand new object every time. We accumulate the new value based on the previous state and the incoming action type. +จะเห็นว่าไม่มี side effects และเรา return object ตัวใหม่ทุกครั้ง เราสร้าง value ขึ้นมาใหม่ โดยอิงจาก state ก่อนหน้าและ action type ที่ถูกส่งเข้ามา -### Wiring to React components +### การเชื่อมต่อกับ React components -If we talk about Redux in the context of React we almost always mean [react-redux](https://github.com/reactjs/react-redux) module. It provides two things that help connecting Redux to our components. +ถ้าเราพูดถึง Redux ที่ใช้ใน React แล้วมักจะหมายถึง [react-redux](https://github.com/reactjs/react-redux) ซึ่งมีสองอย่างที่ช่วยเชื่อมต่อ Redux กับ components ของเรา -(1) `<Provider>` component - it's a component that accepts our store and makes it available for the children down the React tree via the React's context API. For example: +(1) `<Provider>` component - เป็น component ที่รับ store เข้ามาเพื่อทำให้ children node ใน React tree สามารถ access store ได้โดยผ่าน React's context API ตัวอย่างเช่น ```js <Provider store={ myStore }> @@ -99,9 +98,9 @@ If we talk about Redux in the context of React we almost always mean [react-redu </Provider> ``` -We usually have a single place in our app where we use it. +โดยปกติแล้วเราจะประกาศใช้แค่ที่เดียวใน app -(2) `connect` function - it is a function that does the subscribing for updates in the store and re-renders our component. It implements a [higher-order component](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-4/README.md#higher-order-component). Here is its signature: +(2) `connect` function - เป็น function ที่ใช้ subcribe เพื่ออัพเดท store และ re-render component โดย function จะสร้าง [higher-order component](https://github.com/krasimir/react-in-patterns/blob/master/book/chapter-4/README.md#higher-order-component) ออกมา โดย function มีลักษณะดังนี้ ``` connect( @@ -112,7 +111,7 @@ connect( ) ``` -`mapStateToProps` parameter is a function that accepts the current state and must return a set of key-value pairs (an object) that are getting send as props to our React component. For example: +`mapStateToProps` เป็น function ที่รับ current state และ return เป็น set ของ key-value (object) ที่จะถูกส่งไปยัง React component ในรูปของ props ยกตัวอย่างเช่น ```js const mapStateToProps = state => ({ @@ -120,7 +119,7 @@ const mapStateToProps = state => ({ }); ``` -`mapDispatchToProps` is a similar one but instead of the `state` receives a `dispatch` function. Here is the place where we can define a prop for dispatching actions. +`mapDispatchToProps` ทำหน้าที่คล้ายกับ mapStateToProps แต่จะรับ function `dispatch` แทน `state` ซึ่งตรงนี้เป็นที่ ๆ เราจะประกาศ prop สำหรับ dispatch action ```js const mapDispatchToProps = dispatch => ({ @@ -128,21 +127,21 @@ const mapDispatchToProps = dispatch => ({ }); ``` -`mergeProps` combines both `mapStateToProps` and `mapDispatchToProps` and the props send to the component and gives us the opportunity to accumulate better props. Like for example if we need to fire two actions we may combine them to a single prop and send that to React. `options` accepts couple of settings that control how how the connection works. +`mergeProps` เป็นตัวที่รวม `mapStateToProps` และ `mapDispatchToProps` เข้าด้วยกัน เป็นจุดสุดท้ายที่เปลี่ยนแปลงค่า props ก่อนจะส่งไปยัง component จากตัวอย่างด้านบน ถ้าเราต้องการที่จะทำ action สองอย่าง เราสามารถรวมมันเข้าด้วยกันเป็น props เดียวแล้วส่งไปยัง React ได้ `options` รับ setting ไว้สำหรับควบคุมการทำงานของ connect function <br /> -## Simple counter app using Redux +## สร้าง counter app ง่าย ๆ ด้วย Redux -Let's create a simple counter app that uses all the APIs above. +เรามาเริ่มสร้าง counter app แบบง่าย ๆ ที่ใช้ APIs จากด้านบนกัน ![Redux counter app example](./redux-counter-app.png) -The "Add" and "Subtract" buttons will simply change a value in our store. "Visible" and "Hidden" will control its visibility. +ปุ่ม "Add" และ "Subtract" จะเป็นตัวเปลี่ยนแปลงค่าที่อยู่ใน store ของเรา ส่วนปุ่ม "Visible" และ "Hidden" จะเป็นตัวควบคุมการแสดงค่า -### Modeling the actions +### การออกแบบ Actions -For me, every Redux feature starts with modeling the action types and defining what state we want to keep. In our case we have three operations going on - adding, subtracting and managing visibility. So we will go with the following: +สำหรับผมแล้ว ทุก feature ใน Redux จะเริ่มต้นด้วยการออกแบบ action types และประกาศสิ่งที่จะเก็บใน state ในกรณีนี้เรามี 3 operation คือ adding, subtracting และ จัดการ visibility ดังนั้นเราจะมาเริ่มจาก: ```js const ADD = 'ADD'; @@ -156,10 +155,9 @@ const changeVisibility = visible => ({ visible }); ``` +### Store และ Reducers -### Store and its reducers - -There is something that we didn't talk about while explaining the store and reducers. We usually have more then one reducer because we want to manage multiple things. The store is one though and we in theory have only one state object. What happens in most of the apps running in production is that the application state is a composition of slices. Every slice represents a part of our system. In this very small example we have counting and visibility slices. So our initial state looks like that: +ยังมีบางอย่างที่เรายังไม่ได้พูดถึงตอนที่เราอธิบายเรื่อง store กับ reducer โดยปกติแล้วเราจะมี reducer มากกว่าหนึ่งตัว เพราะเราต้องการที่จะแยกจัดการหลาย ๆ อย่าง เรามี store อยู่ตัวเดียวอยู่แล้วและตามทฤษฏีแล้ว state ก็จะมีแค่ตัวเดียวเหมือนกัน โดยส่วนใหญ่ application ที่รันบน production จะมี state ที่ถูกแบ่งเป็นส่วน ๆ แสดงให้เห็นถึงแต่ละส่วนของระบบ ในตัวอย่างเรามีส่วนของ counting และ visibility ซึ่งเราสามารถสร้าง state ได้ตามนั้นเลย ```js const initialState = { @@ -170,9 +168,9 @@ const initialState = { }; ``` -We must define separate reducers for both parts. This is to introduce some flexibility and to improve the readability of our code. Imagine if we have a giant app with ten or more state slices and we keep working within a single function. It will be too difficult to manage. +เราต้องสร้าง reducer แต่ละส่วนแยกกัน ซึ่งทำให้โค้ดของเรามีความยืดหยุ่นและอ่านง่ายมากขึ้น ลองคิดดูว่าถ้าเรามี app ที่มีขนาดใหญ่ที่มีการแบ่ง state มากกว่าสิบส่วน มันคงเป็นการยากที่เราจะต้องจัดการมันอยู่บน function เดียว -Redux comes with a helper that allows us to target a specific part of the state and assign a reducer to it. It is called `combineReducers`: +Redux มาพร้อมกับ function `combineReducers` ที่ทำให้เราสนใจเฉพาะ state ย่อยในแต่ละส่วน และระบุ reducer ให้ state นั้น ```js import { createStore, combineReducers } from 'redux'; @@ -184,9 +182,9 @@ const rootReducer = combineReducers({ const store = createStore(rootReducer); ``` -Function `A` receives only the `counter` slice as a state and needs to return only that part. Same for `B`. Accepts a boolean (the value of `visible`) and must return a boolean. +โดย function `A` จะรับเฉพาะส่วน `counter` จาก state และต้อง return ค่าเฉพาะส่วนนั้น เช่นเดียวกับ `B` ที่จะรับ boolean (ค่าของ `visible`) และต้อง return boolean เท่านั้น -The reducer for our counter slice should take into account both actions `ADD` and `SUBTRACT` and based on them calculates the new `counter` state. +สำหรับ reducer ในส่วนของ counter ควรจะทำงานเมื่อรับ action `ADD` และ `SUBTRACT` มาเพื่อคำนวณหาค่า `counter` state ใหม่ ```js const counterReducer = function (state, action) { @@ -199,9 +197,9 @@ const counterReducer = function (state, action) { }; ``` -Every reducer is fired at least once when the store is initialized. In that very first run the `state` is `undefined` and the `action` is `{ type: "@@redux/INIT"}`. In this case our reducer should return the initial value of our data - `{ value: 0 }`. +reducer ทุกตัวจะถูกเรียกอย่างน้อยหนึ่งครั้งตอนเริ่มสร้าง store โดยค่าเริ่มต้นของ `state` จะเป็น `undefined` และ `action` จะมีค่าเป็น `{ type: "@@redux/INIT"}` ซึ่งในกรณีนี้ reducer ของเราควรจะ return ค่าเริ่มต้นเป็น `{ value: 0 }` -The reducer for the visibility is pretty similar except that it waits for `CHANGE_VISIBILITY` action: +สำหรับ reducer ในส่วนของ visibility นั้นจะมีลักษณะคล้าย ๆ กัน เว้นแต่จะรับ action `CHANGE_VISIBILITY` แทน ```js const visibilityReducer = function (state, action) { @@ -212,7 +210,7 @@ const visibilityReducer = function (state, action) { }; ``` -And at the end we have to pass both reducers to `combineReducers` so we create our `rootReducer`. +และสุดท้ายแล้วเราจะส่ง reducer ทั้งสองตัวไปยัง `combineReducer` เพื่อที่จะสร้างเป็น `rootReducer` ```js const rootReducer = combineReducers({ @@ -223,18 +221,18 @@ const rootReducer = combineReducers({ ### Selectors -Before moving to the React components we have to mention the concept of a *selector*. From the previous section we know that our state is usually divided into different parts. We have dedicated reducers to update the data but when it comes to fetching it we still have a single object. Here is the place where the selectors come in handy. The selector is a function that accepts the whole state object and extracts only the information that we need. For example in our small app we need two of those: +ก่อนจะเริ่มในส่วนถัดไป React components ที่เราได้กล่าวไว้ในส่วน concept ของ *selector* จาก section ที่แล้ว เรารู้แล้วว่า state ของเราถูกแบ่งออกเป็นหลาย ๆ ส่วน เราได้ให้ reducer จัดการเกี่ยวกับการ update data แต่เมื่อไหร่ที่มีการเรียก data เรายังคงมี state object ตัวเดียวอยู่ ซึ่งส่วนนี้ selector จะเป็นตัวช่วยจัดการ โดย selector จะเป็น function ที่รับ state object และเลือก return เฉพาะ data ที่เราต้องการ ยกตัวอย่างเช่น app เล็ก ๆ ของเราต้องการแค่ selector สองตัวนี้ ```js const getCounterValue = state => state.counter.value; const getVisibility = state => state.visible; ``` -A counter app is too small to see the real power of writing such helpers. However, in a big project is quite different. And it is not just about saving a few lines of code. Neither is about readability. Selectors come with these stuff but they are also contextual and may contain logic. Since they have access to the whole state they are able to answer business logic related questions. Like for example "Is the user authorize to do X while being on page Y". This may be done in a single selector. +counter app เล็กเกินกว่าที่จะเห็นประสิทธิภาพจริง ๆ ของการเขียนตัวช่วยพวกนี้ได้ แต่ใน project ใหญ่ ๆ จะต่างกันกันมาก ไม่ใช่แค่เพียงการที่เขียนโค้ดน้อยลงหรืออ่านง่าย เพราะ selector อาจมาพร้อมกับส่วนอื่น ๆ ที่อาจจะมี logic อยู่เนื่องจาก selector สามารถเข้าถึง state ได้ทั้งหมดทำให้สามารถใส่ business logic เพื่อตอบคำถามอย่างเช่น "user มีสิทธิ์ที่จะทำ X ในขณะที่อยู่หน้า Y ได้หรือไม่" ซึ่งสามารถจัดการได้ใน selector เดียว ### React components -Let's first deal with the UI that manages the visibility of the counter. +เรามาเริ่มจัดการกับ UI และส่วนจัดการ visibility ของ counter กันก่อน ```js function Visibility({ changeVisibility }) { @@ -258,9 +256,9 @@ const VisibilityConnected = connect( )(Visibility); ``` -We have two buttons `Visible` and `Hidden`. They both fire `CHANGE_VISIBILITY` action but the first one passes `true` as a value while the second one `false`. The `VisibilityConnected` component class gets created as a result of the wiring done via Redux's `connect`. Notice that we pass `null` as `mapStateToProps` because we don't need any of the data in the store. We just need to `dispatch` an action. +เราต้องการปุ่มสองปุ่มคือ `Visible` กับ `Hidden` ซึ่งทั้งสองปุ่มจะส่ง action `CHANGE_VISIBILITY` แต่ปุ่ม Visible จะส่งค่า `true` ส่วนปุ่ม Hidden จะส่งค่า `false` โดยที่ component class `VisibilityConnected` จะถูกสร้างมาจากการเชื่อมต่อ Redux ด้วย `connect` สังเกตว่าเราส่งค่า `null` เป็นแทน `mapStateToProps` เพราะว่าเราไม่ได้ต้องการ data อะไรจาก store เราแค่ต้องการ `dispatch` action เท่านั้น -The second component is slightly more complicated. It is named `Counter` and renders two buttons and the counter value. +component ที่สองจะซับซ้อนขั้นมากเล็กน้อย โดยที่มันมีชื่อว่า `Counter` และ render ปุ่มสองปุ่มและตัวแสดง counter ```js function Counter({ value, add, subtract }) { @@ -284,12 +282,11 @@ const CounterConnected = connect( )(Counter); ``` -We now need both `mapStateToProps` and `mapDispatchToProps` because we want to read data from the store and dispatch actions. Our component receives three props - `value`, `add` and `subtract`. +คราวนี้เราต้องส่งทั้ง `mapStateToProps` และ `mapDispatchToProps` เพราะว่าเราต้องมีการอ่าน data จาก store และ dispatch action โดย component ของเราจะรับค่า props สามตัวคือ `value`, `add` และ `subtract` -The very last bit is an `App` component where we compose the application. +และสุดท้าย `App` component ที่ ๆ เราจะสร้าง application ```js -function App({ visible }) { return ( <div> <VisibilityConnected /> @@ -304,14 +301,14 @@ const AppConnected = connect( )(App); ``` -We again need to `connect` our component because we want to control the visibility of the counter. The `getVisibility` selector returns a boolean that indicates weather `CounterConnected` will be rendered or not. +เราต้องเรียกใช้ `connect` กับ component อีกครั้ง เพราะเราต้องการที่จะควบคุม visibility ของ counter โดยที่ `getVisibility` selector จะ return ค่า boolean ที่จะเป็นตัวกำหนด `CounterConnected` ว่าจะ render หรือไม่ -## Final thoughts +## ข้อคิด -Redux is a wonderful pattern. Over the years the JavaScript community developed the idea and enhanced it with couple of new terms. I think a typical redux application looks more like this: +Redux เป็น pattern ที่ดี หลายปีแล้วที่ JavaScript community พัฒนาแนวคิดและเพิ่มประสิทธิภาพในหลาย ๆ ด้าน ผมคิดว่ารูปแบบ redux application จะมีหน้าตาใกล้เคียงภาพต่อไปนี้ ![Redux architecture](redux-reallife.jpg) -*By the way we didn't mention the side effects management. It is a whole new story with its own ideas and solutions.* +*อย่างไรก็ตาม เราไม่ได้กล่าวถึงเรื่องการจัดการ side effects ซึ่งถือว่าเป็นเรื่องใหม่ที่มีแนวคิดและวิธีแก้ปัญหาของมันเอง* -We can conclude that Redux itself is a pretty simple pattern. It teaches very useful techniques but unfortunately it is very often not enough. Sooner or later we have to introduce more concepts/patterns. Which of course is not that bad. We just have to plan for it. \ No newline at end of file +เราสามารถสรุปได้ว่า Redux นั้นเป็น pattern ที่เรียบง่าย แถมยังสอนเทคนิคที่มีประโยชน์มาก แต่ในบางครั้งก็ยังไม่พอ ไม่เร็วก็ช้าเราจะมีแนวคิดหรือ pattern ใหม่ ๆ ซึ่งนั่นไม่ใช้เรื่องแย่ เราแค่ต้องเตรียมพร้อมสำหรับมัน diff --git a/book/chapter.txt b/book/chapter.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/book/chapter.txt @@ -0,0 +1 @@ + diff --git a/book/cover.jpg b/book/cover.jpg index 585bb8c..e73e068 100644 Binary files a/book/cover.jpg and b/book/cover.jpg differ diff --git a/book/cover_small.jpg b/book/cover_small.jpg index 88640c8..3a0eba9 100644 Binary files a/book/cover_small.jpg and b/book/cover_small.jpg differ