diff --git a/Ch01/README.md b/Ch01/README.md index 06441ed..b1ab12f 100644 --- a/Ch01/README.md +++ b/Ch01/README.md @@ -1,7 +1,7 @@ -# Ch01 前端工程簡介和 React 生態系簡介 +# Ch01 Web Front-End Development Introduction and React Ecosystem Introduction -1. [Web 前端工程入門簡介](https://github.com/kdchang/reactjs101/blob/master/Ch01/front-end-introduction.md) -2. [React 生態系入門簡介](https://github.com/kdchang/reactjs101/blob/master/Ch01/react-ecosystem-introduction.md) +1. [Web Front-End Development Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch01/front-end-introduction.md) +2. [React Ecosystem Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch01/react-ecosystem-introduction.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch01/front-end-introduction.md b/Ch01/front-end-introduction.md index fa3928c..3915f03 100644 --- a/Ch01/front-end-introduction.md +++ b/Ch01/front-end-introduction.md @@ -1,28 +1,29 @@ -# Web 前端工程入門簡介 +# Web Front-End Development Introduction -![Web 前端工程入門簡介](./images/frameworks.png "Web 前端工程入門簡介") +![Web Front-End Development Introduction](./images/frameworks.png "Web Front-End Development Introduction") -## 前言 -隨著現代化網頁(Modern Web)開發專業和複雜性的提昇以及對於使用者體驗的要求下,網頁開發已從過去的 Web Developer 一夫當關,轉向專業分工,更加細分成網頁前端(Web Front End)、網頁後端(Web Back End)等職位。此外,由於跨平台、跨瀏覽器的需求日益增加,技術變化更迭快速,市場上對於前端工程師(Web Front End Engineer)的需求也與日俱增,前端工程的(Front End Engineering)所要面對的挑戰也越來越多。 +## Foreword +Due to rising complexity within professional modern web development and increasing demand for User Experience(UX), web development has transitioned from the "Web Developer" roles of the past where one developer handled the entire product, to role specialization, adding detailed division of work with roles in Web Front-End and Web Back-End. Additionally as demand for cross-platform and cross-browser support grows and the technologies and skills used rapidly evolve, the market's demand for Web Front-End Engineers also increases by the day, and challenges of Front-End Engineering will be ever increasing. -![Web 前端工程入門簡介](./images/html-css-js.png "Web 前端工程入門簡介") -## 前端工程範疇 -事實上,在目前的業界,前端工程的定位光譜非常廣泛,有聚焦在網頁設計(Web Design),也有專注在軟體工程(Software Engineering)的部份,本書則是將前端工程定位在軟體工程的範疇。而 HTML、CSS 和 JavaScript 是前端工程最重要的技術基礎。過去一段時間,我們所認為的前端工程主要專注在瀏覽器平台,但現在的 Web 平台已經不再侷限於桌面瀏覽器,而是必須面對更多的跨平台、跨瀏覽器的應用開發場景,其中包含: +![Web Front-End Starter Guide Intro](./images/html-css-js.png "Web Front-End Starter Guide Intro") -1. 網頁瀏覽器(Web Browser),一般的網頁應用程式開發 -2. 透過 CLI 指令去操作的 Headless 瀏覽器(Headless Application)。例如:[phantomJS](http://phantomjs.org/)、[CasperJS](http://casperjs.org/) 等 -3. 運作在 WebView 瀏覽器核心(WebView Application)的應用。例如:[Apache Cordova](https://cordova.apache.org/)、[Electron](http://electron.atom.io/)、[NW.js](http://nwjs.io/) 等行動、桌面應用程式開發 -4. 原生應用程式(Native Application),透過 Web 技術撰寫原生應用程式。例如:[React Native](https://facebook.github.io/react-native/)、[Native Script](https://www.nativescript.org/) 等 +## Front-End Development Scope +In truth, currently within the industry, the scope of Front-End Development falls under a wide spectrum, some focus on Web Design alone, and some include aspects of Software Engineering, this book will focus on the latter. HTML, CSS, and Javascript are the most important fundamental skills in Front-End Engineering. Sometime in the past, we knew Front-End Engineering to be focused on browser platforms, but today's Web platform is no longer only about desktop browsers, instead it now encompasses cross-platform, cross-browser application development environments, including: -過去幾年,前端開發就像經歷了文藝復興(Rinascimento)的年代,開始了各種框架、套件百花齊放的時代。雖然現在有更多好用工具可以協助開發,但前端工程師似乎並沒有變得比較輕鬆。以往若能妥善運用 jQuery 等函式庫就可以應付大部分前端工程師的工作,但現在前端徵才廣告上不僅要求精通 HTML、CSS 和 JavaScript,還要對於還要對於 [Backbone](http://backbonejs.org/)、[Ember](http://emberjs.com/)、[Angular](https://angularjs.org/)、[React](https://facebook.github.io/react/)、[Vue](https://vuejs.org/) 等 JavaScript 框架或函式庫有一定程度的了解。 +1. Web Browser, webapp development +2. Using command-line interfaces (CLI) to operate Headless Applications, for example: [phantomJS](http://phantomjs.org/), [CasperJS](http://casperjs.org/), etc +3. WebView Applications. For example: [Apache Cordova](https://cordova.apache.org/), [Electron](http://electron.atom.io/), [NW.js](http://nwjs.io/) to permit mobile and desktop application development. +4. Native Applications written with web development technologies, for example: [React Native](https://facebook.github.io/react-native/), [Native Script](https://www.nativescript.org/), etc. -在眾多 JavaScript 框架或函式庫中,[React](https://facebook.github.io/react/) 是 Facebook 推出的開源 [JavaScript](https://en.wikipedia.org/wiki/JavaScript) Library,它的出現讓許多革新性的 Web 觀念開始流行起來,例如:Virtual DOM、Web Component、更直覺的宣告式 UI 設計、更優雅地實現 Server Rendering 等。接下來本書將透過介紹 React 生態系(ecosystem)帶領讀者入門 React 的世界,讓讀者可以從零開始真的動手用 React 開發跨平台應用程式。 +In the past few years, Front-End development seems to have experienced a Rinascimento (French for renaissance), marking the beginnings of an era with a plethora of various frameworks and modules. Althrough there are now numerous great tools to assist in the development process, the work of a Front-End developer has not become easier. In the past, sensible use of jQuery library methods was sufficient to cover the bulk of a Front-End developer's job, but nowadays recruitment adverts not only ask for mastery in HTML, CSS, and Javascript, they also ask for a specific level of comprehension of JavaScript frameworks and libraries such as [Backbone](http://backbonejs.org/), [Ember](http://emberjs.com/), [Angular](https://angularjs.org/), [React](https://facebook.github.io/react/), [Vue](https://vuejs.org/) etc. -(image via [bsdacademy](http://bsdacademy.com/wp-content/uploads/2014/10/html-css-js.png)、[firebase](https://www.firebase.com/resources/images/website/logos/frameworks.png)) +Amongst the numerous Javascript frameworks and libraries, [React](https://facebook.github.io/react/) is an open-source [JavaScript](https://en.wikipedia.org/wiki/JavaScript) Library developed by Facebook. Its appearance enabled many revolutionary web concepts to become mainstream, for example: Virtual DOM, Web Component, more intuitive declarative UI design, more elegantly realized Server Rendering, etc. The rest of this book will use an introduction to the React ecosystem to introduce readers to the React world, allowing readers to write cross-platform React applications from scratch. -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [下一章:React 生態系(Ecosystem)入門簡介](https://github.com/kdchang/reactjs101/blob/master/Ch01/react-ecosystem-introduction.md) | +(image via [bsdacademy](http://bsdacademy.com/wp-content/uploads/2014/10/html-css-js.png), [firebase](https://www.firebase.com/resources/images/website/logos/frameworks.png)) -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Next article: React Ecosystem Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch01/react-ecosystem-introduction.md) | + +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch01/react-ecosystem-introduction.md b/Ch01/react-ecosystem-introduction.md index fb2235f..58cdf2a 100644 --- a/Ch01/react-ecosystem-introduction.md +++ b/Ch01/react-ecosystem-introduction.md @@ -1,123 +1,126 @@ -# React 生態系(Ecosystem)入門簡介 +# React Ecosystem Introduction -![React 生態系(Ecosystem)入門簡介](./images/react-eco-wp.gif "React 生態系(Ecosystem)入門簡介") +![React Ecosystem Introduction](./images/react-eco-wp.gif "React Ecosystem Introduction") + +According to the explanation from [React's official page](https://facebook.github.io/react/), React is a Javascript Library that specializes in UI/Views. Ever since Facebook publicly launched React as an open-source library in 2013, its ecosystem has expanded rapidly. In fact, through the process of learning about the React ecosystem, we can also pick up important concepts in modern web development (examples: modularization, ES6+, Webpack, Babel, ESLint, functional programming design etc), thereby becoming even better developers. -根據 [React 官方網站](https://facebook.github.io/react/) 的說明:React 是一個專注於 UI(View)的 JavaScript 函式庫(Library)。自從 Facebook 於 2013 年開源 React 這個函式庫後,相關的生態系開始蓬勃發展。事實上,透過學習 React 生態系(ecosystem)的過程中,可以讓我們順便學習現代化 Web 開發的重要觀念(例如:模組化、ES6+、Webpack、Babel、ESLint、函數式程式設計等),成為更好的開發者。 ## ReactJS -ReactJS 是 Facebook 推出的 JavaScript 函式庫,若以 MVC 框架來看,React 定位是在 View 的範疇。在 ReactJS 0.14 版之後,ReactJS 更把原先處理 DOM 的部分獨立出去(react-dom),讓 ReactJS 核心更單純,也更符合 React 所倡導的 `Learn once, write everywhere` 的理念。事實上,ReactJS 本身的 API 相對單純,但由於整個生態系非常龐大,因此學習 React 卻是一條漫長的道路。此外,當你想把 React 應用在你的應用程式時,你通常必須學習整個 React Stack 才能充分發揮 React 的最大優勢。 +ReactJS is a Facebook-developed JavaScript library, if viewed from a MVC framework perspective, React falls under the scope of View(s). In ReactJS v0.14 onwards, ReactJS even isolated the DOM processing feature (react-dom), allowing ReactJS' core to become more pure, fulfilling React's philosophy of `Learn once, write everywhere`. Actually, ReactJS' API is relatively simple, but due to an enormous ecosystem, learning React is a rather long path. Furthermore, when you want to incorporate React in an application, you usually have to learn the entire React Stack to fully unleash React's biggest advantage. + -## JSX -事實上,JSX 並非一種全新的語言,而是一種語法糖([Syntatic Sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)),一種語法類似 [XML](https://zh.wikipedia.org/wiki/XML) 的 ECMAScript 語法擴充。在 JSX 中 HTML 和組建這些元素標籤的程式碼有緊密的關係,這和過去我們強調 HTML、JavaScript 分離的觀念有很大不同。當然,你可以選擇不要在 React 使用 JSX,不過相信我,當你真正開始撰寫 React 元件(Component)時,你會很慶幸有 JSX 真好。 +## JSX +In truth, JSX is not a completely new language, but rather it is ([Syntatic Sugar](https://en.wikipedia.org/wiki/Syntactic_sugar)), with similar syntax as [XML](https://zh.wikipedia.org/wiki/XML) supplementing ECMAScript syntax. JSX code is closely related to the HTML and constructing elements it refers to, this vastly differs from the concept enforced in the past of separating HTML from JavaScript. Of course, you can opt not to use JSX in React, but believe me, when you begin to write React Components, you will be grateful JSX exists. ## NPM -NPM(Node Package Manager)是 Node.js 下的主流套件管理工具。在 NPM 上有非常多的套件,可以讓你不用再重造輪子,更可以讓你可以輕鬆用指令管理不同的套件。由於 NPM 主要是基於 [CommonJS](https://en.wikipedia.org/wiki/CommonJS) 的規範,通常必須搭配 Browserify 這樣的工具才能在前端使用 NPM 的模組。然而因 NPM 是基於 Nested Dependency Tree,不同的套件有可能會在引入依賴時會引入相同但不同版本的套件,造成檔案大小過大的情形。這和另一個套件管理工具 [Bower](https://bower.io/) 專注在前端套件且使用 Flat Dependency Tree(讓使用者決定相依的套件版本)是比較不同的地方。 +Node Package Manager (NPM) is the mainstream package manager for Node.js. There are numerous packages in NPM, so you will not have to reinvent the wheel, and it allows you to manage all packages with ease. Because NPM follows [CommonJS](https://en.wikipedia.org/wiki/CommonJS) specifications, usually a tool like Browserify must be used to use NPM modules in the front-end. But because NPM relies on a Nested Dependency Tree, packages could potentially require different versions of dependent packages, causing a bloat in file size. This is a notable difference between NPM and another package manager [Bower](https://bower.io/), which specializes in front-end packages and utilizes a Flat Dependency Tree (permits users to decide on the versions for dependent packages). ## ES6+ -[ES6+](https://babeljs.io/blog/2015/06/07/react-on-es6-plus) 係指 ES6(ES2015)和 ES7 的聯集,在 ES6+ 新的標準當中引入許多新的特性和功能,彌補了過去 JavaScript 被詬病的一些特性。由於未來 React 將以支援 ES6+ 為主,因此直接學習 ES6+ 用法是相對好的選擇,本書的所有範例也將會以 ES6+ 撰寫。 +[ES6+](https://babeljs.io/blog/2015/06/07/react-on-es6-plus) collectively refers to ES6(ES2015) and ES7, in the new specification under ES6+, many new characteristics and features were introduced, filling in the lacking points of past versions of JavaScript. Because in the future React will mainly support ES6+, going straight to learning ES6+ is a relatively sound choice, this book will use ES6+ for all code examples. ## Babel -由於並非所有瀏覽器都支援 ES6+ 語法,所以透過 [Babel](https://babeljs.io/) 這個 JavaScript 編譯器(可以想成是翻譯機或是翻譯蒟篛)可以讓你的 ES6+ 、JSX 等程式碼轉換成瀏覽器可以看得懂的語法。通常會在資料夾的 root 位置加入 `.babelrc` 進行轉譯規則 `preset` 和引用外掛(plugin)的設定。 +Because not all browsers support ES6+ syntax, we can use [Babel](https://babeljs.io/), a JavaScript pre-processor (you can think of it as a translator), allowing your ES6+ and JSX code to be converted to a syntax that the browser can understand. Usually in the root of project folders we will add a `.babelrc` for translation `preset`s and plugin configurations. + +## modularized JavaScript development +As WebApps increased in complexity, modularized Javascript development became an inevitable trend, below I show a simple introduction to Javascript modularization and its related rules. In fact, in the early days when there was no official specification, there were an assortment of different standards developed and practised by various communities. -## JavaScript 模組化開發 -隨著 Web 應用程式的複雜性提高,JavaScript 模組化開發已經成為必然的趨勢,以下簡單介紹 JavaScript 模組化的相關規範。事實上,在一開始沒有官方定義的標準時出現了各種社群自行定義的規範和實踐。 1. CDN-Based - 也就是最傳統的 ` - +
- + - \ No newline at end of file + diff --git a/Ch02/webpack-dev-enviroment.md b/Ch02/webpack-dev-enviroment.md index c4beb2c..ebe9b6a 100644 --- a/Ch02/webpack-dev-enviroment.md +++ b/Ch02/webpack-dev-enviroment.md @@ -1,30 +1,33 @@ -# React 開發環境設置與 Webpack 入門教學 +# React Development Environment Settings and Introduction to Webpack -![React 開發環境設置與 Webpack 入門教學](./images/react-webpack-browserify.png "React 開發環境設置與 Webpack 入門教學") +![React Development Environment Settings and Introduction to Webpack](./images/react-webpack-browserify.png "React Development Environment Settings and Introduction to Webpack") -## 前言 -俗話說工欲善其事,必先利其器。寫程式也是一樣,搭建好開發環境後可以讓自己在後續開發上更加順利。因此本章接下來將討論 React 開發環境的兩種主要方式:CDN-based、 [webpack](https://webpack.github.io/)(這邊我們就先不討論 [TypeScript](https://www.typescriptlang.org/) 的開發方式)。至於 [browserify](https://webpack.github.io/) 搭配 [Gulp](http://gulpjs.com/) 的方法則會放在補充資料中,讓讀者閱讀完本章後可以開始 React 開發之旅! +## Foreword +As the proverb goes: "to do great work, one must have sharp tools". Writing programs is much the same, by properly establishing the development environment, we pave our way for a smoother development process. Therefore this chapter will discuss the two main setups for React development environments: CDN-based and [webpack](https://webpack.github.io/) (here we will omit the discussion on [TypeScript](https://www.typescriptlang.org/) development workflow). As for the method of pairing [browserify](https://webpack.github.io/) with [Gulp](http://gulpjs.com/), this will be included in the supplemental section, allowing readers to begin their React development journey after reading this article! -## JavaScript 模組化 -隨著網站開發的複雜度提昇,許多現代化的網站已不是單純的網站而已,更像是個富有互動性的網頁應用程式(Web App)。為了應付現代化網頁應用程式開發的需求,解決一些像是全域變數污染、低維護性等問題,JavaScript 在模組化上也有長足的發展。過去一段時間讀者們或許聽過像是 `Webpack`、`Browserify`、`module bundlers`、`AMD`、`CommonJS`、`UMD`、`ES6 Module` 等有關 JavaScript 模組化開發的專有名詞或工具,在前面一個章節我們也簡單介紹了關於 JavaScript 模組化的簡單觀念和規範介紹。若是讀者對於 JavaScript 模組化開發尚不熟悉的話推薦可以參考 [這篇文章](http://huangxuan.me/2015/07/09/js-module-7day/) 和 [這篇文章](https://medium.freecodecamp.com/javascript-modules-a-beginner-s-guide-783f7d7a5fcc#.oa2n5s5zt) 當作入門。 +## JavaScript modularization -總的來說,使用模組化開發 JavaScript 應用程式主要有以下三種好處: +As website development becomes increasingly complex, many modern websites are no longer simply websites, but rather Web Apps with great interactive potential. In order to meet the demand in modern web app development, as well as provide solutions that guard against global namespace pollution and low maintainability issues, Javascript modularization has made significant progress. A while ago readers may have heard of things like `Webpack`, `Broswerify`, `module bundlers`, `AMD`, `CommonJS`, `UMD`, `ES6 Module` etc tools and terms related to Javascript modularized development, in our previous chapter we also briefly touched on the basic concept and scope of Javascript modularization. If the reader is still lacking clarity with regard to this topic, I recommend reading [this article](http://huangxuan.me/2015/07/09/js-module-7day/) as well as [this article](https://medium.freecodecamp.com/javascript-modules-a-beginner-s-guide-783f7d7a5fcc#.oa2n5s5zt) as primer. -1. 提昇維護性(Maintainability) -2. 命名空間(Namespacing) -3. 提供可重用性(Reusability) +All said, using modularized Javascript applications mainly provides 3 benefits: -而在 React 應用程式開發上更推薦使用像是 `Webpack` 這樣的 `module bundlers` 來組織我們的應用程式,但對於一般讀者來說 `Webpack` 強大而完整的功能相對複雜。為了讓讀者先熟悉 `React` 核心觀念(我們假設讀者已經有使用 `JavaScript` 或 `jQuery` 的基本經驗),我們將從使用 `CDN` 引入 ` - +
- + @@ -115,7 +117,7 @@ class HelloMessage extends React.Component {
``` -- 從外部引入 +- importing from outside: `` -### 2. 標籤用法 -JSX 標籤非常類似 XML ,可以直接書寫。一般 Component 命名首字大寫,HTML Tags 小寫。以下是一個建立 Component 的 class: +### 2. Tag usage +JSX tags are highly similar to XML, they can be written directly. Usually we capitalize the Component names, and write HTML Tags in lowercase. Below is a class that builds a Component: ```js class HelloMessage extends React.Component { @@ -159,19 +161,19 @@ class HelloMessage extends React.Component { } ``` -### 3. 轉換成 JavaScript +### 3. Converting to Javascript -JSX 最終會轉換成瀏覽器可以讀取的 JavaScript,以下為其規則: +JSX gets converted to browser-readable Javascript in the end, below are its rules: ```js React.createElement( - string/ReactClass, // 表示 HTML 元素或是 React Component - [object props], // 屬性值,用物件表示 - [children] // 接下來參數皆為元素子元素 + string/ReactClass, // represents HTML element or React Component + [object props], // property values, represented with object + [children] // the following variables are child elements ) ``` -解析前(特別注意在 JSX 中使用 JavaScript 表達式時使用 `{}` 括起,如下方範例的 `text`,裡面對應的是變數。若需放置一般文字,請加上 `''`): +Before parsing (pay note that Javascript expressions in JSX are wrapped in `{}`, in the below example `text` contains a corresponding variable. To place regular text, add `''`): ```js var text = 'Hello React'; @@ -179,40 +181,40 @@ var text = 'Hello React';

{'text'}

``` -解析完後: +After parsing: ```js var text = 'Hello React'; React.createElement("h1", null, "Hello React!"); ``` -另外要特別要注意的是由於 JSX 最終會轉成 JavaScript 且每一個 JSX 節點都對應到一個 JavaScript 函數,所以在 Component 的 `render` 方法中只能回傳一個根節點(Root Nodes)。例如:若有多個 `
` 要 `render` 請在外面包一個 Component 或 `
`、`` 元素。 +Another point to note is due to JSX inevitably being converted to Javascript, every JSX node corresponds to a Javascript function, therefore the `render` method in Component can only return one root node. For example: if many `
`s exist under `render`, please wrap them in an additional parent `
` or `` type element. -### 4. 註解 -由於 JSX 最終會編譯成 JavaScript,註解也一樣使用 `//` 和 `/**/` 當做註解方式: +### 4. Comments +Because JSX eventually is converted to JavaScript, comments also use `//` and `/**/`: ```js -// 單行註解 +// single line comment /* - 多行註解 + multiline comment */ var content = ( - {/* 若是在子元件註解要加 {} */} + {/* comments within a child element must be wrapped in {} */} ); ``` -### 5. 屬性 -在 HTML 中,我們可以透過標籤上的屬性來改變標籤外觀樣式,在 JSX 中也可以,但要注意 `class` 和 `for` 由於為 JavaScript 保留關鍵字用法,因此在 JSX 中使用 `className` 和 `htmlFor` 替代。 +### 5. Properties +In HTML, we can adjust the appearance of elements based on their tags, this can also be done in JSX, but be careful that `class` and `for` are reserved namespaces in Javascript, therefore JSX instead uses `className` and `htmlFor` as substitute. ```js class HelloMessage extends React.Component { @@ -226,23 +228,23 @@ class HelloMessage extends React.Component { } ``` -#### Boolean 屬性 -在 JSX 中預設只有屬性名稱但沒設值為 `true`,例如以下第一個 input 標籤 `disabled ` 雖然沒設值,但結果和下面的 input 為相同: +#### Boolean properties +In JSX properties have a default name but no boolean default `true`, for example the first input tag `disabled` lacks a predefined value for `disabled`, however although there is no default value, the result is the same as the input tag below: ```html ; ; ``` -反之,若是沒有屬性,則預設預設為 `false`: +Instead, if there is no property mentioned, its default is set to `false`: ```html ; ; ``` -### 6. 擴展屬性 -在 ES6 中使用 `...` 是迭代物件的意思,可以把所有物件對應的值迭代出來設定屬性,但要注意後面設定的屬性會蓋掉前面相同屬性: +### 6. expanded properties +In ES6 `...` signifies the object to be iteratively replaced, it is possible to iteratively set property values, but take care that the later settings on each property will override previous ones if they share the same name. ```js var props = { @@ -253,50 +255,50 @@ var props = { -// 等於以下 +// is equivalent the following React.createElement("h1", React._spread({}, props, {value: "yo"}), "Hello React!"); ``` -### 7. 自定義屬性 -若是希望使用自定義屬性,可以使用 `data-`: +### 7. self-defined properties +To self-define properties, use `data-`: ```js ``` -### 8. 顯示 HTML -通常為了避免資訊安全問題,我們會過濾掉 HTML,若需要顯示的話可以使用: +### 8. Displaying HTML +Usually due to data security, we filter out HTML, but if you want to display it you can use: ```html
{{_html: '

Hello World!!

'}}
``` -### 9. 樣式使用 -在 JSX 中使用外觀樣式方法如下,第一個 `{}` 是 JSX 語法,第二個為 JavaScript 物件。與一般屬性值用 `-` 分隔不同,為駝峰式命名寫法: +### 9. using Styles +In JSX styles may be applied as follows, the first `{}` is JSX syntax, the second is for Javascript objects. It is different from normal style property which uses `-` as delimiter, instead camelCase is used: ```js ``` -### 10. 事件處理 -事件處理為前端開發的重頭戲,在 JSX 中透過 inline 事件的綁定來監聽並處理事件(注意也是駝峰式寫法),更多事件處理方法請[參考官網](https://facebook.github.io/react/docs/events.html#supported-events) +### 10. event handling +Event handling is the "main event" of front-end development, in JSX through in-line event binding we can watch for and respond to events (pay note this also uses camelCase) for more on event handling please [see official documentation](https://facebook.github.io/react/docs/events.html#supported-events). ```js ``` -## 總結 -以上就是 JSX 簡明入門教學,希望透過以上介紹,讓讀者了解在 React 中為何要使用 JSX,以及 JSX 基本概念和用法。最後為大家複習一下:在 React 世界裡,所有事物都是以 Component 為基礎,通常會將同一個 Component 相關的程式和資源都放在一起,而在撰寫 React Component 時我們常會使用 [JSX](https://facebook.github.io/jsx/) 的方式來提升程式撰寫效率。JSX 是一種語法類似 XML 的 ECMAScript 語法擴充,可以善用 JavaScript 的強大能力,放棄蹩腳的模版語言。當然 JSX 並非強制使用,你也可以選擇不用,因為最終 JSX 的內容會轉化成 JavaScript。當相信閱讀完上述的內容後,你會開始認真考慮使用 JSX 的語法。 +## Summary +Above is our JSX Simple Starter Guide, hopefully through the introduction above readers now understand the reasons to use JSX in React, as well as the foundational concepts and usages of JSX. Let us conclude with a review: In the React World, everything is built upon Components, usually we place a Component and its relevant code and resources together, and while writing React Components we usually use [JSX](https://facebook.github.io/jsx/) to increase writing efficiency. JSX is an extension for ECMAScript with similar syntax to XML, capable of exemplifying Javascript's formidable capabilities, discarding its past as a shoddy templating language. Of course JSX is not mandatory, and you can choose not to use it, because in the end JSX content is converted to JavaScript. After reading the above content, you will seriously consider using JSX syntax. -## 延伸閱讀 +## Extended Reading 1. [Imperative programming or declarative programming](http://www.puritys.me/docs-blog/article-320-Imperative-programming-or-declarative-programming.html) 2. [JSX in Depth](https://facebook.github.io/react/docs/jsx-in-depth.html) -3. [從零開始學 React(ReactJS 101)](https://www.gitbook.com/book/kdchang/react101/details) +3. [ReactJS 101 (lit. "Learn ReactJS From Zero")](https://www.gitbook.com/book/kdchang/react101/details) -(image via [adweek](http://www.adweek.com/socialtimes/files/2014/05/LikeButtoniOSApps650.jpg), [codecondo](http://codecondo.com/wp-content/uploads/2015/12/Useful-Features-of-React_7851.png)) +(image via [adweek](http://www.adweek.com/socialtimes/files/2014/05/LikeButtoniOSApps650.jpg), [codecondo](http://codecondo.com/wp-content/uploads/2015/12/Useful-Features-of-React_7851.png)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:ReactJS 與 Component 設計入門介紹](https://github.com/kdchang/reactjs101/blob/master/Ch03/reactjs-introduction.md) | [下一章:Props、State、Refs 與表單處理](https://github.com/kdchang/reactjs101/blob/master/Ch04/props-state-introduction.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: ReactJS and Component Design Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch03/reactjs-introduction.md) | [Next article: Props, State, Refs and form handling](https://github.com/sycherng/reactjs101/blob/en-US/Ch04/props-state-introduction.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch03/reactjs-introduction.md b/Ch03/reactjs-introduction.md index a0c0971..2fed775 100644 --- a/Ch03/reactjs-introduction.md +++ b/Ch03/reactjs-introduction.md @@ -1,26 +1,28 @@ -# ReactJS 與 Component 設計入門介紹 +# ReactJS and Component Design Introduction -## 前言 -在上一個章節中我們快速學習了 React 開發環境建置和 Webpack 入門。接下來我們將更進一步了解 React 和 Component 設計時需注意的幾個重要特性。 +## Foreword +In the previous chapter we quickly learned about React development environment configuration and basic Webpack concepts. Now we will deepen our understanding of several important characteristics to pay attention to during React and Component design. -## ReactJS 特性簡介 -React 原本是 Facebook 自己內部使用的開發工具,但卻是一個目標遠大的一個專案:`Learn once, write anywhere`。自從 2013 年開源後周邊的生態系更是蓬勃發展。ReactJS 的出現讓前端開發有許多革新性的思維出現,其中有幾個重要特性值得我們去探討: +## ReactJS characteristics introduction +React originally was a development tool used internally at Facebook, yet it was a project with a very lofty goal: `Learn once, write anywhere`. Ever since its open-source release in 2013 its ecosystem has expanded rapidly. The appearance of ReactJS allowed front-end development to see many revolutionary new lines of thinking to emerge, of which there are a few major characteristics worthy of our inspection: -1. 基於元件(Component)化思考 -2. 用 JSX 進行宣告式(Declarative)UI 設計 -3. 使用 Virtual DOM -4. Component PropType 防呆機制 -5. Component 就像個狀態機(State Machine),而且也有生命週期(Life Cycle) -6. 一律重繪(Always Redraw)和單向資料流(Unidirectional Data Flow) -7. 在 JavaScript 裡寫 CSS:Inline Style +1. Thinking in terms of Components +2. Using JSX to carry out Declarative UI design +3. Use of a Virtual DOM +4. Component PropType error-proofing mechanism +5. Components are like State Machines, in that they also have Life Cycles +6. "Always Redraw" and Unidirectional Data Flow +7. Writing Inline Style CSS within Javascript -## 基於元件(Component)化思考 +## Thinking in terms of Components -![ReactJS 與 Component 設計入門介紹](./images/component.png "ReactJS 與 Component 設計入門介紹") -在 React 的世界中最基本的單元為元件(Component),每個元件也可以包含一個以上的子元件,並依照需求組裝成一個組合式的(Composable)元件,因此具有封裝(encapsulation)、關注點分離 (Separation of Concerns)、複用 (Reuse) 、組合 (Compose) 等特性。 +![ReactJS and Component Design Introduction](./images/component.png "ReactJS and Component Design Introduction") -`` 元件可以包含 ``、`` 子元件 +In the world of React, the most fundamental units are Components, every component may have child components, and may be combined into a Composable unit according to build needs, in doing so allowing characteristics such as encapsulation, Separation of Concerns, Reuse, and Compose. + + +`` component may include ``, `` child components ```javascript
@@ -28,29 +30,30 @@ React 原本是 Facebook 自己內部使用的開發工具,但卻是一個目
``` -`` 元件內部長相: +Inside the `` component: ```javascript
    -
  • 寫程式碼
  • -
  • 哄妹子
  • -
  • 買書
  • +
  • write code
  • +
  • flirt with girls
  • +
  • purchase books
``` -元件化一直是網頁前端開發的聖杯,許多開發者最希望的就是可以最大化重複使用(reuse)過去所寫的程式碼,不要重複造輪子(DRY)。在 React 中元件是一切的基礎,讓開發應用程式就好像在堆積木一樣。然而對於過去習慣模版式(template)開發的前端工程師來說,短時間要轉換成元件化思考模式並不容易,尤其過去我們往往習慣於將 HTML、CSS 和 JavaScript 分離,現在卻要把它們都封裝在一起。 +The change towards using Components is a holy grail within web fron-end development, many developers' biggest wish is to have maximal reuse of written code (aka DRY code), avoiding duplicate work. In React, components are the foundation to everything, allowing the app development process to seem like building with bricks. However for those front-end developers who in the past have become accustomed to using templates in their development, it is not easy to switch to component-based thinking within a short timeframe, especially when we have become used to separation of HTML, CSS and JavaScript, only to now have to put them all together. -一個比較好的方式就是訓練自己看到不同的網頁或應用程式時,強迫自己將看到的頁面切成一個個元件。相信過了一段時間後,天眼開了,就比較容易習慣元件化的思考方式。 +A rather optimal way to train yourself is when viewing various webpages or applications, force yourself to see the entire website in terms of individual components. Have faith that after a period of time, your eyes will open, and become familiarized with thinking in terms of components. -以下是一般 React Component 撰寫的主要兩種方式: -1. 使用 ES6 的 Class(可以進行比較複雜的操作和元件生命週期的控制,相對於 stateless components 耗費資源) +Below are the two common ways to write React Components: + +1. Using Class from ES6 (allows more complex operations and control over the lifecycle of the component, more resource intensive than stateless components) ```javascript - // 注意元件開頭第一個字母都要大寫 + // pay note to capitalize the first letter of Component class MyComponent extends React.Component { - // render 是 Class based 元件唯一必須的方法(method) + // render is the only method that is mandatory for Class based components render() { return (
Hello, World!
@@ -58,52 +61,52 @@ React 原本是 Facebook 自己內部使用的開發工具,但卻是一個目 } } - // 將 元件插入 id 為 app 的 DOM 元素中 + // Insert the component named into the DOM element with id set to 'app' ReactDOM.render(, document.getElementById('app')); ``` -2. 使用 Functional Component 寫法(單純地 render UI 的 stateless components,沒有內部狀態、沒有實作物件和 ref,沒有生命週期函數。若非需要控制生命週期的話建議多使用 stateless components 獲得比較好的效能) +2. Writing a Functional Component (stateless components to be rendered simply in the UI, there is no actual object with ref, no lifecycle variable. If you do not need to control the lifecycle, stateless components are recommended to achieve better performance) ```javascript - // 使用 arrow function 來設計 Functional Component 讓 UI 設計更單純(f(D) => UI),減少副作用(side effect) + // Use arrow function to designate Functional Components allowing UI design to be simpler(f(D) => UI), reducing side effects const MyComponent = () => (
Hello, World!
); - // 將 元件插入 id 為 app 的 DOM 元素中 + // Insert the component named into the DOM element with id set to 'app' ReactDOM.render(, document.getElementById('app')); ``` -## 用 JSX 進行宣告式(Declarative)UI 設計 -React 在設計上的思路認為使用 Component 比起模版(Template)和顯示邏輯(Display Logic)更能實現關注點分離的概念,而搭配 JSX 可以實現聲明式 Declarative(注重 what to),而非命令式 Imperative(注重 how to)的程式撰寫方式。 +## Using JSX to carry out Declarative UI design +React's design follows the belief that the use of Components allows better separation of concerns as compared to Template and Display Logic based design, pairing with JSX enables Declarative (focus on "what to") as opposed to Imperative ("how to") programming style. -像下述的宣告式(Declarative)UI 設計就比單純用(Template)式的方式更易懂: +For example the below Declarative style UI design is easier to understand than a design only using Templates: ```javascript -// 使用宣告式(Declarative)UI 設計很容易可以看出這個元件的功能 +// Use of Declarative UI design makes it easy to see the purpose of this component ``` ```javascript -// 內部長相 +// How looks on the inside:
``` -由於 JSX 在 React 元件撰寫上扮演很重要的角色,因此在下一個章節我們也將更深入講解 JSX 使用細節。 +Because JSX plays a key role in writing React components, we will cover JSX usage in more detail in the next chapter. -## 使用 Virtual DOM -在傳統 Web 中一般是使用 jQuery 進行 DOM 的直接操作。然而更改 DOM 往往是 Web 效能的瓶頸,因此在 React 世界設計有 Virtual DOM 的機制,讓 App 和 DOM 之間用 Virtual DOM 進行溝通。當更改 DOM 時,會透過 React 自身的 diff 演算法去計算出最小更新,進而去最小化更新真實的 DOM。 +## Use of a Virtual DOM +It is common in the traditional Web to use jQuery for direct handling of DOM operations. Changes in the DOM was frequently a bottleneck for Web performance, therefore the React world provides a Virtual DOM mechanism, allowing the App and DOM to communicate through the Virtual DOM. When the DOM is altered, React's internal diff calculates the smallest update needed, and executes those minimal updates on the real DOM. -## Component PropType 防呆機制 -在 React 設計時除了提供 props 預設值設定(Default Prop Values)外,也提供了 Prop 的驗證(Validation)機制,讓整個 Component 設計更加穩健: +## Component PropType error-proofing mechanism +The design in React not only provides props with Default Prop Values, it also provides Prop Validation mechanisms, allowing the design of the entire Component to be more robust: ```javascript -// 注意元件開頭第一個字母都要大寫 +// pay note to capitalize thf first letter in Component class MyComponent extends React.Component { - // render 是 Class based 元件唯一必須的方法(method) + // render is the only mandatory method in Class based components render() { return (
Hello, World!
@@ -111,29 +114,30 @@ class MyComponent extends React.Component { } } -// PropTypes 驗證,若傳入的 props type 不符合將會顯示錯誤 +// PropTypes validation, if prop type is invalid an error is displayed MyComponent.propTypes = { todo: React.PropTypes.object, name: React.PropTypes.string, } -// Prop 預設值,若對應 props 沒傳入值將會使用 default 值 +// Prop default values, if no parameters are given to props it will take on the default values MyComponent.defaultProps = { todo: {}, name: '', } ``` -關於更多的 Validation 用法可以參考[官方網站](https://facebook.github.io/react/docs/reusable-components.html) 的說明。 +For more ways to use Validation check out the instructions at [official webpage](https://facebook.github.io/react/docs/reusable-components.html). + +## Components are like State Machines, in that they also have Life Cycles +Components act like a State Machine, depending on the varying state (edited via `setState()`) and props (provided by the parent component), Components will display a corresponding result. And as humans age and pass away, components also have life cycles. Through manipulation of the life cycle veriable, we can specify exact times for the Component to handle tasks, a more detailed introduction on Component lifecycles will be covered in the next chapter. -## Component 就像個狀態機(State Machine),而且也有生命週期(Life Cycle) -Component 就像個狀態機(State Machine),根據不同的 state(透過 `setState()` 修改)和 props(由父元素傳入),Component 會出現對應的顯示結果。而人有生老病死,元件也有生命週期。透過操作生命週期處理函數,可以在對應的時間點進行 Component 需要的處理,關於更詳細的元件生命週期介紹我們會再下一個章節進行更一步說明。 +## "Always Redraw" and Unidirectional Data Flow +In the React world, props and state are important features that affect the appearance of React Components. In particular props are conveyed from the parent component and cannot be changed. On the other hand state is altered according to interactions with the user, it mainly changes through the setState() method. When React discovers a new change in props or state, it will completely redraw the UI. Of course you can always use forceUpdate() method to forcibly redraw a component. Paired with Flux or Flux-like architectures (for example: Redux), React can realize Unidirectional Data Flow in an even more concrete way, providing heightened clarity for data flow management. -## 一律重繪(Always Redraw)和單向資料流(Unidirectional Data Flow) -在 React 世界中,props 和 state 是影響 React Component 長相的重要要素。其中 props 都是由父元素所傳進來,不能更改,若要更改 props 則必須由父元素進行更改。而 state 則是根據使用者互動而產生的不同狀態,主要是透過 setState() 方法進行修改。當 React 發現 props 或是 state 更新時,就會重繪整個 UI。當然你也可以使用 forceUpdate() 去強迫重繪 Component。而 React 透過整合 Flux 或 Flux-like(例如:Redux)可以更具體實現單向資料流(Unidirectional Data Flow),讓資料流的管理更為清晰。 -## 在 JavaScript 裡寫 CSS:Inline Style -在 React Component 中 CSS 使用 Inline Style 寫法,全都封裝在 JavaScript 當中: +## Writing Inline Style CSS within Javascript +Within a React Component, Inline Style CSS can be wrapped within Javascript: ```javascript const divStyle = { @@ -144,28 +148,28 @@ const divStyle = { ReactDOM.render(
Hello World!
, document.getElementById('app')); ``` -## 總結 -以上介紹了 ReactJS 的幾個重要特性: +## Summary +Above we covered several key characteristics of ReactJS:: -1. 基於元件(Component)化思考 -2. 用 JSX 進行宣告式(Declarative)UI 設計 -3. 使用 Virtual DOM -4. Component PropType 防呆機制 -5. Component 就像個狀態機(State Machine),而且也有生命週期(Life Cycle) -6. 一律重繪(Always Redraw)和單向資料流(Unidirectional Data Flow) -7. 在 JavaScript 裡寫 CSS:Inline Style +1. Thinking in terms of Components +2. Using JSX to carry out Declarative UI design +3. Use of a Virtual DOM +4. Component PropType error-proofing mechanism +5. Components are like State Machines, in that they also have Life Cycles +6. "Always Redraw" and Unidirectional Data Flow +7. Writing Inline Style CSS within Javascript -接下來我們將進一步探討 React 裡 JSX 的使用方式。 +In the next part we will investigate the usage of JSX in React with more detail. -## 延伸閱讀 -1. [React 入门实例教程](http://www.ruanyifeng.com/blog/2015/03/react.html) +## Extended Reading +1. [React Example-Based Primer Course](http://www.ruanyifeng.com/blog/2015/03/react.html) 2. [React Demystified](http://blog.reverberate.org/2014/02/react-demystified.html) 3. [Top-Level API](https://facebook.github.io/react/docs/top-level-api.html) 4. [ES6 Classes Component](https://facebook.github.io/react/docs/reusable-components.html#es6-classes) -(image via [maketea](http://maketea.co.uk/images/2014-03-05-robust-web-apps-with-react-part-1/wireframe_deconstructed.png)) +(image via [maketea](http://maketea.co.uk/images/2014-03-05-robust-web-apps-with-react-part-1/wireframe_deconstructed.png)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:React 開發環境設置與 Webpack 入門教學](https://github.com/kdchang/reactjs101/blob/master/Ch02/webpack-dev-enviroment.md) | [下一章:JSX 簡明入門教學指南](https://github.com/kdchang/reactjs101/blob/master/Ch03/react-jsx-introduction.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article:React Development Environment Settings and Introduction to Webpack](https://github.com/sycherng/reactjs101/blob/en-US/Ch02/webpack-dev-enviroment.md) | [Next article: JSX Simple Starter Guide](https://github.com/sycherng/reactjs101/blob/en-US/Ch03/react-jsx-introduction.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch03/todo-examples/.gitignore b/Ch03/todo-examples/.gitignore deleted file mode 100644 index 823c495..0000000 --- a/Ch03/todo-examples/.gitignore +++ /dev/null @@ -1,38 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (http://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules -jspm_packages - -# Optional npm cache directory -.npm - -# Optional REPL history -.node_repl_history \ No newline at end of file diff --git a/Ch03/todo-examples/app/components/TodoHeader/TodoHeader.js b/Ch03/todo-examples/app/components/TodoHeader/TodoHeader.js index b86c091..2b23815 100644 --- a/Ch03/todo-examples/app/components/TodoHeader/TodoHeader.js +++ b/Ch03/todo-examples/app/components/TodoHeader/TodoHeader.js @@ -10,7 +10,7 @@ const TodoHeader = (props) => ( className="form-control" type="text" /> - +
); diff --git a/Ch04/README.md b/Ch04/README.md index 22163d7..3b6a38d 100644 --- a/Ch04/README.md +++ b/Ch04/README.md @@ -1,7 +1,7 @@ -# Ch04 Props/State 基礎與 Component 生命週期 +# Props/State Basics and Component Lifecycles -1. [Props、State、Refs 與表單處理](https://github.com/kdchang/reactjs101/blob/master/Ch04/props-state-introduction.md) -2. [React Component 規格與生命週期(Life Cycle)](https://github.com/kdchang/reactjs101/blob/master/Ch04/react-component-life-cycle.md) +1. [Props, State, Refs and form handling](https://github.com/sycherng/reactjs101/blob/en-US/Ch04/props-state-introduction.md) +2. [React Component Specification and Life Cycle](https://github.com/sycherng/reactjs101/blob/en-US/Ch04/react-component-life-cycle.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch04/props-state-introduction.md b/Ch04/props-state-introduction.md index 551b453..23e80d2 100644 --- a/Ch04/props-state-introduction.md +++ b/Ch04/props-state-introduction.md @@ -1,10 +1,10 @@ -# Props、State、Refs 與表單處理 +# Props, State, Refs and form handling -## 前言 -在前面的章節中我們已經對於 React 和 JSX 有初步的認識,我們也了解到 React Component 事實上可以視為顯示 UI 的一個狀態機(state machine),而這個狀態機根據不同的 state(透過 `setState()` 修改)和 props(由父元素傳入),Component 會出現對應的顯示結果。本章將使用 [React 官網首頁上的範例](https://facebook.github.io/react/index.html)(使用 ES6+)來更進一步說明 Props 和 State 特性及在 React 如何進行事件和表單處理。 +## Foreword +In the previous chapter we already touched on the basics of React and JSX, we also understood that React Component can be seen as an UI state machine, and this state machine alters the way Components are displayed through different state (edited via `setState()`) and props (transferred from parent element). This chapter will follow [the demo from React's official web landing page](https://facebook.github.io/react/index.html) (using ES6+) to provide a deeper explanation on Props and State characteristics and how to handle forms and events in React. ## Props -首先我們使用 React 官網上的 A Simple Component 來說明 props 的使用方式。由於傳入元件的 name 屬性為 Mark,故以下程式碼將會在瀏覽器顯示 Hello, Mark。針對傳入的 props 我們也有驗證和預設值的設計,讓我們撰寫的元件可以更加穩定健壯(robust)。 +First we'll use the "A Simple Component" example from React's official website to illustrate how to use props. Because the imported component has a name property set to Mark, the below code will show Hello, Mark in the browser. Aiming to benefit from the design characteristic of props having default values, we can write a component that is more robust. HTML Markup: @@ -17,7 +17,7 @@ HTML Markup: A Component Using External Plugins - +
@@ -26,17 +26,17 @@ HTML Markup: ``` -app.js,使用 ES6 Class Component 寫法: +app.js, using ES6 Class Component syntax: ```javascript class HelloMessage extends React.Component { - // 若是需要綁定 this.方法或是需要在 constructor 使用 props,定義 state,就需要 constructor。若是在其他方法(如 render)使用 this.props 則不用一定要定義 constructor + // if you wish to bind this.method, want to use props, or define state within the constructor, you will need a constructor. If you are using this.props elsewhere (such as in render) you do not have to declare a constructor constructor(props) { - // 對於 OOP 物件導向程式設計熟悉的讀者應該對於 constructor 建構子的使用不陌生,事實上它是 ES6 的語法糖,骨子裡還是 prototype based 物件導向程式語言。透過 extends 可以繼承 React.Component 父類別。super 方法可以呼叫繼承父類別的建構子 + // those readers familiar with object-oriented programming (OOP) will find the constructor structure familiar, in actuality it is ES6 syntax sugar, behind the scenes it is still a prototype based language. Through extends we can inherit from React.Component parent class. super method calls the constructor of the parent class super(props); this.state = {} } - // render 是唯一必須的方法,但如果是單純 render UI 建議使用 Functional Component 寫法,效能較佳且較簡潔 + // render is the only mandatory method, but if you are simply rendering UI I recommend using Functional Components instead, it will enable improved performance and cleaner code render() { return (
Hello {this.props.name}
@@ -44,12 +44,12 @@ class HelloMessage extends React.Component { } } -// PropTypes 驗證,若傳入的 props type 不是 string 將會顯示錯誤 +// PropTypes validation, if the props type passed in is not string, an error will be displayed HelloMessage.propTypes = { name: React.PropTypes.string, } -// Prop 預設值,若對應 props 沒傳入值將會使用 default 值 Zuck +// Prop default value, if the corresponding props lacks an incoming value, it will default to Zuck HelloMessage.defaultProps = { name: 'Zuck', } @@ -57,22 +57,22 @@ HelloMessage.defaultProps = { ReactDOM.render(, document.getElementById('app')); ``` -關於 React ES6 class constructor super() 解釋可以參考 [React ES6 class constructor super()](http://cheng.logdown.com/posts/2016/03/26/683329) 。 +An explanation regarding React ES6 class constructor super() can be found at [React ES6 class constructor super()](http://cheng.logdown.com/posts/2016/03/26/683329). -使用 Functional Component 寫法: +Using Functional Components: ```javascript -// Functional Component 可以視為 f(d) => UI,根據傳進去的 props 繪出對應的 UI。注意這邊 props 是傳入函式的參數,因此取用 props 不用加 this +// Functional Component can be seen as f(d) => UI, drawing onto the UI according to the incoming props. pay note here props is a variable passed into a function, so we use props without adding this const HelloMessage = (props) => (
Hello {props.name}
); -// PropTypes 驗證,若傳入的 props type 不是 string 將會顯示錯誤 +// PropTypes validation, if the props type passed in is not string, an error will be displayed HelloMessage.propTypes = { name: React.PropTypes.string, } -// Prop 預設值,若對應 props 沒傳入值將會使用 default 值 Zuck。用法等於 ES5 的 getDefaultProps +// Prop default value, if the corresponding props lacks an incoming value, it will default to Zuck. Usage is equivalent to getDefaultProps from ES5 HelloMessage.defaultProps = { name: 'Zuck', } @@ -80,12 +80,12 @@ HelloMessage.defaultProps = { ReactDOM.render(, document.getElementById('app')); ``` -在 jsbin 上的範例: +A demo hosted on jsbin: A Component Using External Plugins on jsbin.com ## State -接下來我們將使用 A Stateful Component 這個範例來講解 State 的用法。在 React Component 可以自己管理自己的內部 state,並用 `this.state` 來存取 state。當 `setState()` 方法更新了 state 後將重新呼叫 `render()` 方法,重新繪製 component 內容。以下範例是一個每 1000 毫秒(等於1秒)就會加一的累加器。由於這個範例是 Stateful Component 因此僅使用 ES6 Class Component,而不使用 Functional Component。 +Now we will use a Stateful Component to demonstrate the usage of State. In React, Components can self-manage their internal state, and use `this.state` to read from state. When `setState()` method updates state, it will then call `render()`, to redraw the component contents. Below is an example of an accumulator program that adds one every 1000 ms (equivalent to 1 second). Because this is a Stateful Component, we can only use an ES6 Class Component, we cannot use a Functional Component. HTML Markup: @@ -112,26 +112,26 @@ app.js: class Timer extends React.Component { constructor(props) { super(props); - // 與 ES5 React.createClass({}) 不同的是 component 內自定義的方法需要自行綁定 this context,或是使用 arrow function + // a difference from ES5 React.createClass({}) is that the component's declaration must bind this context, or use an arrow function this.tick = this.tick.bind(this); - // 初始 state,等於 ES5 中的 getInitialState + // initialize state, equivalent to getInitialState in ES5 this.state = { secondsElapsed: 0, } } - // 累加器方法,每一秒被呼叫後就會使用 setState() 更新內部 state,讓 Component 重新 render + // accumulator method, call setState() every second to update internal state, and cause re-rendering of the Component tick() { this.setState({secondsElapsed: this.state.secondsElapsed + 1}); } - // componentDidMount 為 component 生命週期中階段 component 已插入節點的階段,通常一些非同步操作都會放置在這個階段。這便是每1秒鐘會去呼叫 tick 方法 + // componentDidMount is the mounting stage of a component's lifecycle, usually all synchronous operations are placed in this stage. Here we call tick every second componentDidMount() { this.interval = setInterval(this.tick, 1000); } - // componentWillUnmount 為 component 生命週期中 component 即將移出插入的節點的階段。這邊移除了 setInterval 效力 + // componentWillUnmount is the stage of a component lifecycle when the component has been unmounted. Here we remove setInterval's effect componentWillUnmount() { clearInterval(this.interval); } - // render 為 class Component 中唯一需要定義的方法,其回傳 component 欲顯示的內容 + // render is the only mandatory function in class Components, it is used to return the content the component wants to display render() { return (
Seconds Elapsed: {this.state.secondsElapsed}
@@ -142,10 +142,10 @@ class Timer extends React.Component { ReactDOM.render(, document.getElementById('app')); ``` -關於 Javascript this 用法可以參考 [Javascript:this用法整理](https://software.intel.com/zh-cn/blogs/2013/10/09/javascript-this)。 +Regarding this usage of Javascript this you can refer to [Javascript:this usage](https://software.intel.com/zh-cn/blogs/2013/10/09/javascript-this). -## 事件處理(Event Handle) -在前面的內容我們已經學會如何使用 props 和 state,接下來我們要更進一步學習在 React 內如何進行事件處理。下列將使用 React 官網的 An Application 當做例子,實作出一個簡單的 TodoApp。 +## Event handling +In earlier content we learned how to use props and state, now we will advance our knowledge by learning how to handle events within React. Below we use an example titled "An Application" from React's official website, and create a simple TodoApp. HTML Markup: @@ -169,7 +169,7 @@ HTML Markup: app.js: ```javascript -// TodoApp 元件中包含了顯示 Todo 的 TodoList 元件,Todo 的內容透過 props 傳入 TodoList 中。由於 TodoList 僅單純 Render UI 不涉及內部 state 操作是 stateless component,所以使用 Functional Component 寫法。需要特別注意的是這邊我們用 map function 來迭代 Todos,需要留意的是每個迭代的元素必須要有 unique key 不然會發生錯誤(可以用自定義 id,或是使用 map function 的第二個參數 index) +// TodoApp component includes a TodoList component, Todo's content is fed into TodoList via props. Because TodoList simply Renders UI and does not involve operations on internal state, it is a stateless component, therefore we choose to write it as a Functional Component. Take care that here we used map function to iteratively provide our Todos, every element in our iterator must have a unique key or an error will occur (the unique key may be a self-defined id, or a second variable index provided through map function). const TodoList = (props) => (
    { @@ -180,7 +180,7 @@ const TodoList = (props) => (
) -// 整個 App 的主要元件,這邊比較重要的是事件處理的部份,內部有 +// The main App component, pay note to the event handling, it looks like this inside: class TodoApp extends React.Component { constructor(props) { super(props); @@ -217,12 +217,12 @@ class TodoApp extends React.Component { ReactDOM.render(, document.getElementById('app')); ``` -以上介紹了 React 事件處理的部份,除了 `onChange` 和 `onSubmit` 外,React 也封裝了常用的事件處理,如 `onClick` 等。若想更進一步了解有哪些可以使用的事件處理方法可以參考 [官網的 Event System](https://facebook.github.io/react/docs/events.html)。 +Above I have introduced React event handling, in addition to `onChange` and `onSubmit`, React comes with many common event handlers such as `onClick`. If you wish to obtain a deeper understanding on the types of supported event handling methods, please refer to [The official documentation on Event System](https://facebook.github.io/react/docs/events.html)。 -## Refs 與表單處理 -上面介紹了 props(傳入後就不能修改)、state(隨著使用者互動而改變)和事件處理機制後,我們將接續介紹如何在 React 中進行表單處理。同樣我們使用 React 官網範例 A Component Using External Plugins 進行介紹。由於 React 可以容易整合外部的 libraries(例如:jQuery),本範例將使用 `remarkable` 結合 `ref` 屬性取出 DOM Value 值(另外比較常用的作法是使用 `onChange` 事件處理方式處理表單內容),讓使用者可以使用 Markdown 語法的所見即所得編輯器(editor)。 +## Refs and form handling +Above we introduced props (which cannot be edited after it is introduced), state (changes according to interaction with user) and event handling, next we will introduce how to handle forms in React. Once again we will use an official example from React's webpage, "A Component Using External Plugins" for our introduction. Because React easily integrates external libraries (ex: jQuery), this example will use `remarkable` combined with `ref` props to extract DOM Value (another common usage is using `onChange` handler to handle form content), allowing users to use the straightforward Markdown editor. -HTML Markup(除了引入 `react` 、 `react-dom` 還要用 `CDN` 方式引入 `remarkable` 這個 `Markdown` 語法 parser 套件,記得如果沒有使用 Webpack 或是 browserify + babelify 等工具需要引入 `babel-standalone` 瀏覽器解析 ES6 語法並於引入 script 加上 type="text/babel"): +HTML Markup (In addition to importing `react` and `react-dom` we also use `CDN` import for `remarkable`, a `Markdown` syntax parser plugin, remember if you do not use Webpack or browserify + babelify related tools you need to import `babel-standalone` ES6 syntax parser for the browser and add type="text/babel" within the import +Click here to see a detailed demo -![React Component 規格與生命週期](./images/react-lifecycle.png) +![React Component Specification and Life Cycle](./images/react-lifecycle.png) -其中特殊處理的函數 `shouldComponentUpdate`,目前預設 `return true`。若你想要優化效能可以自己編寫判斷方式,若採用 `immutable` 可以使用 `nextProps === this.props` 比對是否有變動: +In particular the special method `shouldComponentUpdate` currently has the default `return true`. If you want to optimize performance you can code your own logic, if using `immutable` you can test with `nextProps === this.props` to compare whether changes took place: ```javascript shouldComponentUpdate(nextProps, nextState) { @@ -151,8 +152,8 @@ shouldComponentUpdate(nextProps, nextState) { } ``` -## Ajax 非同步處理 -若有需要進行 Ajax 非同步處理,請在 `componentDidMount` 進行處理。以下透過 `jQuery` 執行 `Ajax` 取得 `Github API` 資料當做範例: +## Ajax asynchronous processing +If you require Ajax asynchronous processing, please handle these within `componentDidMount`. Below we use `jQuery` to handle `Ajax` to receive `Github API` data as an example: HTML Markup: @@ -215,17 +216,17 @@ ReactDOM.render( ); ``` -點擊看詳細範例 +Click here to see a detailed demo -## 總結 -以上介紹了 React Component 規格與生命週期(Life Cycle)的概念,其中生命週期的概念對於初學者來說可能會比較抽象,建議讀者跟著範例動手實作。接下來我們將更進一步介紹 `React Router` 讓讀者感受一下單頁式應用程式(single page application)的設計方式。 +## Summary +Above we introduced React Component Specifications and the Lifecycle concept, among which the lifecycle concept may be rather abstract for beginners, I recommend readers to follow the demos by physically doing each step. In the next part we will be giving a more in-depth introduction on `React Router` so readers can experience single page application design. -## 延伸閱讀 +## Extended Reading 1. [Component Specs and Lifecycle](https://facebook.github.io/react/docs/component-specs.html#lifecycle-methods) -(image via [react-lifecycle](http://imgh.us/react-lifecycle.svg)) +(image via [react-lifecycle](http://imgh.us/react-lifecycle.svg)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:Props、State、Refs 與表單處理](https://github.com/kdchang/reactjs101/blob/master/Ch04/props-state-introduction.md) | [下一章:React Router 入門實戰教學](https://github.com/kdchang/reactjs101/blob/master/Ch05/react-router-introduction.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Props, State, Refs and form handling](https://github.com/sycherng/reactjs101/blob/en-US/Ch04/props-state-introduction.md) | [下一章:Learn by Writing: React Router Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch05/react-router-introduction.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch05/README.md b/Ch05/README.md index 40afaa8..4d42017 100644 --- a/Ch05/README.md +++ b/Ch05/README.md @@ -1,6 +1,6 @@ # Ch05 React Router -1. [React Router 入門實戰教學](https://github.com/kdchang/reactjs101/blob/master/Ch05/react-router-introduction.md) +1. [Learn by Writing: React Router Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch05/react-router-introduction.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch05/react-router-introduction.md b/Ch05/react-router-introduction.md index a858a11..58f88ec 100644 --- a/Ch05/react-router-introduction.md +++ b/Ch05/react-router-introduction.md @@ -1,22 +1,23 @@ -# React Router 入門實戰教學 +# Learn by Writing: React Router Introduction -![React Router 資料夾結構](./images/react-router.jpg "React Router 入門實戰教學") +![React Router directory structure](./images/react-router.jpg "Learn by Writing: React Router Introduction") -## 前言 -若你是從一開始一路走到這裡讀者請先給自己一個愛的鼓勵吧!在經歷了 React 基礎的訓練後,相信各位讀者應該都等不及想大展拳腳了!接下來我們將進行比較複雜的應用程式開發並和讀者介紹目前市場上常見的不刷頁單頁式應用程式(single page application)的設計方式。 +## Foreword +If you have read up to here from the start please give yourself a round of applause! After covering all the React basic training, I'm sure everyone cannot wait to use their new skills! In the following portion we will develop a more complex application and introduce to the reader how to design single page applications which are so commonly seen on the market. -## 單頁式應用程式(single page application) -傳統的 Web 開發主要是由伺服器管理 URL Routing 和渲染 HTML 頁面,過往每次 URL 一換或使用者連結一點,就需要重新從伺服器端重新載入頁面。但隨著使用者對於使用者體驗的要求提昇,許多的網頁應用程式紛紛設計成不刷頁的單頁式應用程式(single page application),由前端負責 URL 的 routing 管理,若需要和後端進行 API 資料溝通的話,通常也會使用 Ajax 的技術。在 React 開發世界中主流是使用 [react-router](https://github.com/reactjs/react-router) 這個 routing 管理用的 library。 -## React Router 環境設置 +## single page application +Traditional web development mainly uses the server to manage URL Routing and HTML rendering of the page, in the past every time the URL changed or the user clicked a link, the entire page had to be re-rendered from the server. But as users began to increase their demands on their user experience, many web applications began to design single page applications which do not reload pages, handles URL routing from the front-end, and if requiring communication with a backend API, usually utilizes Ajax technology. In the React development world the mainstream [react-router](https://github.com/reactjs/react-router) is the common library used for routing management. -先透過以下指令在根目錄產生 npm 設定檔 `package.json` : +## React Router Environment Setup + +First use the below command to generate within the root directory a npm configurations file `package.json`: ``` $ npm init ``` -安裝相關套件(包含開發環境使用的套件): +Install related packages (including packages used in the dev environment): ```shell $ npm install --save react react-dom react-router @@ -26,13 +27,13 @@ $ npm install --save react react-dom react-router $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es2015 babel-preset-react eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react webpack webpack-dev-server html-webpack-plugin ``` -安裝好後我們可以設計一下我們的資料夾結構,首先我們在根目錄建立 `src` 和 `res` 資料夾,分別放置 `script` 的 `source` 和靜態資源(如:全域使用的 `.css` 和圖檔)。在 `components` 資料夾中我們會放置所有 `components`(個別元件資料夾中會用 `index.js` 輸出元件,讓引入元件更簡潔),其餘設定檔則放置於根目錄下。 +After installation we can do some design of our folder structure, first within the project root folder create `src` and `res` directories, to hold `script` and `source` static resources respectively (example: global `.css` and image files). In the `components` directory we will place all of our `components` (each component folder will use `index.js` to export components, making component imports cleaner), the remaining configuration files will be placed under our project root folder. -![React Router 資料夾結構](./images/folder.png "React Router 資料夾結構") +![React Router directory structure](./images/folder.png "React Router directory structure") -接下來我們先設定一下開發文檔。 +Now we will modify our development-related files. -1. 設定 Babel 的設定檔: `.babelrc` +1. Configure the Babel settings file: `.babelrc` ```javascript { @@ -45,7 +46,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ``` -2. 設定 ESLint 的設定檔和規則: `.eslintrc` +2. Configure the ESLint settings file and rules: `.eslintrc` ```javascript { @@ -59,10 +60,10 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 } ``` -3. 設定 Webpack 設定檔: `webpack.config.js` +3. Configure the Webpack settings file: `webpack.config.js` ```javascript - // 讓你可以動態插入 bundle 好的 .js 檔到 .index.html + // allowing you to dynamically insert bundled .js files to .index.html const HtmlWebpackPlugin = require('html-webpack-plugin'); const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ @@ -71,7 +72,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 inject: 'body', }); - // entry 為進入點,output 為進行完 eslint、babel loader 轉譯後的檔案位置 + // entry is our entrypoint, output is where files go after eslint and babel loader translations module.exports = { entry: [ './src/index.js', @@ -98,7 +99,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 }, }], }, - // 啟動開發測試用 server 設定(不能用在 production) + // settings for development server (cannot be used in production) devServer: { inline: true, port: 8008, @@ -107,9 +108,9 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 }; ``` -太好了!這樣我們就完成了開發環境的設定可以開始動手實作 `React Router` 應用程式了! +Awesome! This concludes our development environment setup so we can start working on our `React Router` application! -## 開始 React Routing 之旅 +## Embarking on the journey of React Routing HTML Markup: @@ -127,39 +128,40 @@ HTML Markup: ``` -以下是 `webpack.config.js` 的進入點 `src/index.js`,負責管理 `Router` 和 `render` 元件。這邊我們要先詳細討論的是,為了使用 React Router 功能引入了許多 `react-router` 內部的元件。 +Below is `webpack.config.js` entrypoint `src/index.js`, which handles `Router` and `render` components. Here we should first discuss in detail, the many `react-router` components we imported in order to use the React Router. 1. Router -`Router` 是放置 Route 的容器,其本身不定義 routing ,真正 routing 規則由 `Route` 定義。 +`Router` is a container for Route, it does not define routing, the real routing rules are defined by `Route`. 2. Route -`Route` 負責 URL 和對應的元件關係,可以有多個 `Route` 規則也可以有嵌套(nested)`Routing`。像下面的例子就是每個頁面都會先載入 `App` 元件再載入對應 URL 的元件。 +`Route` manages the relationship between URL and corresponding components, there can be many `Route` rules or there can be nested `Routing`. Such as in the below example every page first loads `App` component then loads the components corresponding to each URL. 3. history -`Router` 中有一個屬性 `history` 的規則,這邊使用我們使用 `hashHistory`,使用 routing 將由 `hash`(#)變化決定。例如:當使用者拜訪 `http://www.github.com/`,實際看到的會是 `http://www.github.com/#/`。下列範例若是拜訪了 `/about` 則會看到 `http://localhost:8008/#/about` 並載入 `App` 元件再載入 `About` 元件。 +`Router` has an attribute `history`, here we use `hashHistory`, using `hash`(#) changes to adjust our routing. For example: when the user visits `http://www.github.com/`, they will actually see `http://www.github.com/#/`. In the below example if instead `/about` is visited `http://localhost:8008/#/about` is shown and `App` component loads which then loads `About` component. - hashHistory - 教學範例使用的,會通過 `hash` 進行對應。好處是簡單易用,不用多餘設定。 + used for our demo, responding based on `hash`. The benefit is ease of use, no extra configuration is required. - browserHistory - 適用於伺服器端渲染,但需要設定伺服器端避免處理錯誤,這部份我們會在後面的章節詳細說明。注意的是若是使用 Webpack 開發用伺服器需加上 `--history-api-fallback` + suited for server side rendering, but requires configuration to prevent server handling error, we will provide more detail on this in later chapters. Pay note if using the dev server in Webpack you must add `--history-api-fallback` ``` $ webpack-dev-server --inline --content-base . --history-api-fallback ``` - createMemoryHistory - 主要用於伺服器渲染,使用上會建立一個存在記憶體的 `history` 物件,不會修改瀏覽器的網址位置。 + maiinly used in server rendering, its use creates a `history` object in memory, the browser url is not altered. ``` const history = createMemoryHistory(location) ``` 4. path -`path` 是對應 URL 的規則。例如:`/repos/torvalds` 會對應到 `/repos/:name` 的位置,並將參數傳入 `Repos` 元件中。由 `this.props.params.name` 取得參數。順帶一提,若為查詢參數 `/user?q=torvalds` 則由 `this.props.location.query.q` 取得參數。 +`path` is the rule corresponding to the URL. For example: `/repos/torvalds` corresponds to the position `/repos/:name`, additionally it sends variables into the `Repos` component. Use `this.props.params.name` to access the variable. While we're on this subject, if you need to look up variable `/user?q=torvalds`, use `this.props.location.query.q` to access that variable. 5. IndexRoute -由於 `/` 情況下 App 元件對應的 `this.props.children` 會是 `undefinded`,所以使用 `IndexRoute` 來解決對應問題。這樣當 URL 為 `/` 時將會對應到 Home 元件。不過要注意的是 `IndexRoute` 沒有 path 屬性。 +Because in the context of `/`, the App component's corresponding `this.props.children` is `undefined`, we use `IndexRoute` to address this issue. This way when URL is `/` it will correspond to the Home element. But pay note that `IndexRoute` does not have a path property. + ```javascript import React from 'react'; @@ -184,7 +186,7 @@ ReactDOM.render( , document.getElementById('app')); - /* 另外一種寫法: + /* Another way to write this: const routes = ( @@ -201,18 +203,18 @@ ReactDOM.render( */ ``` -由於我們在 `index.js` 使用嵌套 routing,把 App 元件當做每個元件都會載入的母模版,亦即進入每個對應頁面載入對應元件前都會先載入 App 元件。這樣就可以讓每個頁面都有導覽列連結可以點選,同時可以透過 `props.children` 載入對應 URL 的子元件。 +Because we used nested routing in `index.js`, the App component was used as a parent template for every child component, meaning before each corresponding page is loaded, App component will always be loaded first. This allows every page to have shared clickable navigation links, and at the same time the child components corresponding to the URL can be loaded via `props.children`. 1. Link -`Link` 元件主要用於點擊後連結轉換,可以想成是 `` 超連結的 React 版本。若是希望當點擊時候有對應的 css style,可以使用 `activeStyle`、`activeClassName` 去做設定。範例分別使用於 `index.html`使用傳統 `CSS` 載入、Inline Style、外部引入 `Inline Style` 寫法。 +`Link` component is mainly used for linking upon clicks, it can be thought of like React's version of `` anchor hyperlinks. If a corresponding css style upon clicks is desired, `activeStyle` and `activeClassName` can be used to make these configurations. Our example uses original `CSS` imports within `index.html`, Inline Styles, and externally imported `Inline Style` syntax respectively. 2. IndexLink -IndexLink 主要是了處理 `index` 用途,特別注意當 child route `actived` 時,parent route 也會 `actived`。所以我們回首頁的連結使用 `` 內部的 `onlyActiveOnIndex` 屬性來解決這個問題。 +IndexLink is mainly used to manage `index`, pay special note when a child route is `actived`, the parent route is also `actived`. Therefore our link to return to the homepage uses `onlyActiveOnIndex` props within ``to resolve this problem. -3. Redirect、IndexRedirect -這邊雖然沒有用到,但若讀者有需要使用到連結跳轉的話可以參考這兩個元件,用法類似於 `Route` 和 `IndexRedirect`。 +3. Redirect, IndexRedirect +Although it was not used here, if the reader needs to use links that redirect these two components may be worth investigating, the usage is similar to `Route` and `IndexRedirect`. -以下是 `src/components/App/App.js` 完整程式碼: +Below is the complete code for `src/components/App/App.js`: ```javascript import React from 'react'; @@ -230,7 +232,7 @@ const App = (props) => (
  • User
  • Contacts
  • - + {props.children}
    ); @@ -242,9 +244,9 @@ App.propTypes = { export default App; ``` -對應的元件內部使用 Functional Component 進行 UI 渲染: +We use Functional Components within the corresponding components to render the UI -以下是 `src/components/Repos/Repos.js` 完整程式碼: +Below is the full code for `src/components/Repos/Repos.js`: ```javascript import React from 'react'; @@ -263,23 +265,23 @@ Repos.propTypes = { export default Repos; ``` -詳細的程式碼讀者可以參考範例資料夾,若讀者跟著範例完成的話,可以在終端機上執行 `npm start`,並於瀏覽器 `http://localhost:8008`看到以下成果,當你點選連結時會切換對應元件並改變 `actived` 狀態! +For detailed code the reader can take a look at the demo folders, if any readers followed the demo to completion, `npm start` may be used in the terminal, and the result can be seen at `http://localhost:8008` in the browser, when you click on navigation links the components will switch according to their `actived` state! -![範例成果](./images/example.png "範例成果") +![Example result](./images/example.png "Example result") -## 總結 -到這邊我們又一起完成了一個重要的一關,學習 `routing` 對於使用 `React` 開發複雜應用程式是非常重要的一步,接下來我們將一起學習一個相對獨立的單元 `ImmutableJS`,但學習 `ImmutableJS` 可以讓我們在使用 `React` 和 `Flux/Redux` 可以有更好的效能和避免一些副作用。 +## Summary +Now we have overcome an important milestone together, learning `routing` is a very important step in using `React` to develop complex applications, in the next part together we will learn about a relatively independent unit `ImmutableJS`, but learning `ImmutableJS` can allow us to have better performance and avoid several side effects when we use `React` with `Flux/Redux`. -## 延伸閱讀 +## Extended Reading 1. [Leveling Up With React: React Router](https://css-tricks.com/learning-react-router/) 2. [Programmatically navigate using react router](http://stackoverflow.com/questions/31079081/programmatically-navigate-using-react-router) -3. [React Router 使用教程](http://www.ruanyifeng.com/blog/2016/05/react_router.html) -4. [React Router 中文文档](https://react-guide.github.io/react-router-cn/index.html) +3. [React Router Usage Course](http://www.ruanyifeng.com/blog/2016/05/react_router.html) +4. [React Router Chinese Documentation](https://react-guide.github.io/react-router-cn/index.html) 5. [React Router Tutorial](https://github.com/reactjs/react-router-tutorial) -(iamge via [seanamarasinghe](http://seanamarasinghe.com/wp-content/uploads/2016/01/react-router-1050x360.jpg)) +(iamge via [seanamarasinghe](http://seanamarasinghe.com/wp-content/uploads/2016/01/react-router-1050x360.jpg)) -## 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:React Component 規格與生命週期(Life Cycle)](https://github.com/kdchang/reactjs101/blob/master/Ch04/react-component-life-cycle.md) | [下一章:ImmutableJS 入門教學](https://github.com/kdchang/reactjs101/blob/master/Ch06/react-immutable-introduction.md) | +## Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: React Component Specification and Life Cycle](https://github.com/sycherng/reactjs101/blob/en-US/Ch04/react-component-life-cycle.md) | [Next article: ImmutableJS Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch06/react-immutable-introduction.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch06/README.md b/Ch06/README.md index a1f1286..db6b321 100644 --- a/Ch06/README.md +++ b/Ch06/README.md @@ -1,6 +1,6 @@ # Ch06 ImmutableJS -1. [ImmutableJS 入門教學](https://github.com/kdchang/reactjs101/blob/master/Ch06/react-immutable-introduction.md) +1. [ImmutableJS Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch06/react-immutable-introduction.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101) | diff --git a/Ch06/react-immutable-introduction.md b/Ch06/react-immutable-introduction.md index 89bdacf..af8e756 100644 --- a/Ch06/react-immutable-introduction.md +++ b/Ch06/react-immutable-introduction.md @@ -1,9 +1,9 @@ -# ImmutableJS 入門教學 +# ImmutableJS Introduction ![ImmutableJS](./images/immutable.png "ImmutableJS") -## 前言 -一般來說在 JavaScript 中有兩種資料類型:Primitive(String、Number、Boolean、null、undefinded)和 Object(Reference)。在 JavaScript 中物件的操作比起 Java 容易很多,但也因為相對彈性不嚴謹,所以產生了一些問題。在 JavaScript 中的 Object(物件)資料是 Mutable(可以變的),由於是使用 Reference 的方式,所以當修改到複製的值也會修改到原始值。例如下面的 `map2` 值是指到 `map1`,所以當 `map1` 值一改,`map2` 的值也會受影響。 +## Foreword +Usually in Javascript there are two data categories: Primitive (String, Number, Boolean, null, undefinded) and Object (Reference). In JavaScript the manipulation of objects is a lot easier than in Java, yet as a result of the relative flexability and permissive nature, some problems are created. In JavaScript Object data is mutable, due to the use of Reference method, therefore editing a cloned value will inadvertently alter the source value. An example is shown below where `map2`'s value points to `map1`, so once the value of `map1` is changed, `map2`'s value is also impacted. ```javascript var map1 = { a: 1 }; @@ -11,11 +11,11 @@ var map2 = map1; map2.a = 2 ``` -通常一般作法是使用 `deepCopy` 來避免修改,但這樣作法會產生較多的資源浪費。為了很好的解決這個問題,我們可以使用 `Immutable Data`,所謂的 Immutable Data 就是一旦建立,就不能再被修改的數據資料。 +Usually this is avoided with `deepCopy`, but this strategy wastes a lot of resources. In order to have a better solution, we can make use of `Immutable Data`, the meaning of Immutable Data is data that upon establishment, can no longer be altered. -為了解決這個問題,在 2013 年時 Facebook 工程師 Lee Byron 打造了 [ImmutableJS](https://facebook.github.io/immutable-js/),但並沒有被預設放到 React 工具包中(雖然有提供簡化的 Helper),但 `ImmutableJS` 的出現確實解決了 `React` 甚至 `Redux` 所遇到的一些問題。 +In order to solve this problem, in 2013 Facebook developer Lee Byron created [ImmutableJS](https://facebook.github.io/immutable-js/), but this did not get included by default in the React kit (although there is a simplified Helper), but the appearance of `ImmutableJS` in fact solved problems encountered in `React`, even `Redux`. -以下範例即是引入了 `ImmutableJS` 的效果,讀者可以發現,雖然我們操作了 `map1` 的值,但會發現原本的 `map1` 並未受到影響(因為任何修改都不會影響到原始資料),雖然使用 `deepCopy` 也可以模擬類似的效果但會浪費過多的計算資源和記憶體,`ImmutableJS` 則可以容易地共享沒有被修該到的資料(例如下面的資料 `b` 即為 `map1` 所 `map2` 共享),因而有更好的效能表現。 +The example below uses the effect of `ImmutableJS`, the reader may discover although we made changes to the value of `map1`, the original source value is not affected (because no changes will affect the source data), although the use of `deepCopy` creates a similar effect, it also creates a large amount of processing resources and memory, instead `ImmutableJS` easily shares unaltered data (for example below the data `b` is shared between `map1` and `map2`), permitting better performance. ```javascript import Immutable from 'immutable'; @@ -27,73 +27,73 @@ map1.get('a'); // 1 map2.get('a'); // 2 ``` -## ImmutableJS 特性介紹 -ImmutableJS 提供了 7 種不可修改的資料類型:`List`、`Map`、`Stack`、`OrderedMap`、`Set`、`OrderedSet`、`Record`。若是對 Immutable 物件操作都會回傳一個新值。其中比較常用的有 `List`、`Map` 和 `Set`: +## ImmutableJS Characteristics Intro +ImmutableJS provides 7 kinds of immutable data types: `List`, `Map`, `Stack`, `OrderedMap`, `Set`, `OrderedSet`, `Record`. Any operations on Immutable objects will return a new value. Among these the more commonly used ones are `List`, `Map` and `Set`: -1. Map:類似於 key/value 的 object,在 ES6 也有原生 `Map` 對應 +1. Map: an object with key/value, in ES6 it corresponds to native `Map` ```javascript const Map= Immutable.Map; - // 1. Map 大小 + // 1. Map size const map1 = Map({ a: 1 }); map1.size // => 1 - // 2. 新增或取代 Map 元素 + // 2. Create or replace a Map key // set(key: K, value: V) const map2 = map1.set('a', 7); // => Map { "a": 7 } - // 3. 刪除元素 + // 3. delete key // delete(key: K) const map3 = map1.delete('a'); // => Map {} - // 4. 清除 Map 內容 + // 4. clear map content const map4 = map1.clear(); // => Map {} - // 5. 更新 Map 元素 + // 5. update Map key // update(updater: (value: Map) => Map) // update(key: K, updater: (value: V) => V) // update(key: K, notSetValue: V, updater: (value: V) => V) const map5 = map1.update('a', () => (7)) // => Map { "a": 7 } - // 6. 合併 Map + // 6. merge Maps const map6 = Map({ b: 3 }); map1.merge(map6); // => Map { "a": 1, "b": 3 } ``` -2. List:有序且可以重複值,對應於一般的 Array +2. List: ordered and allows duplicate values, corresponds to normal Arrays ```javascript const List= Immutable.List; - // 1. 取得 List 長度 + // 1. Get List length const arr1 = List([1, 2, 3]); arr1.size // => 3 - // 2. 新增或取代 List 元素內容 + // 2. Create or replace List elements // set(index: number, value: T) - // 將 index 位置的元素替換 + // swap elements by indices const arr2 = arr1.set(-1, 7); // => [1, 2, 7] const arr3 = arr1.set(4, 0); // => [1, 2, 3, undefined, 0] - // 3. 刪除 List 元素 + // 3. deleting elements // delete(index: number) - // 刪除 index 位置的元素 + // delete an element at a certain index const arr4 = arr1.delete(1); // => [1, 3] - // 4. 插入元素到 List + // 4. insert element into List // insert(index: number, value: T) - // 在 index 位置插入 value + // insert value at index const arr5 = arr1.insert(1, 2); // => [1, 2, 2, 3] @@ -103,41 +103,41 @@ ImmutableJS 提供了 7 種不可修改的資料類型:`List`、`Map`、`Stack // => [] ``` -3. Set:沒有順序且不能重複的列表 +3. Set: unordered and no duplicate elements allowed ```javascript const Set= Immutable.Set; - // 1. 建立 Set + // 1. create Set const set1 = Set([1, 2, 3]); // => Set { 1, 2, 3 } - // 2. 新增元素 + // 2. add new element const set2 = set1.add(1).add(5); // => Set { 1, 2, 3, 5 } - // 由於 Set 為不能重複集合,故 1 只能出現一次 + // because Set prohibits duplicate elements, 1 only appears once - // 3. 刪除元素 + // 3. delete an element const set3 = set1.delete(3); // => Set { 1, 2 } - // 4. 取聯集 + // 4. union const set4 = Set([2, 3, 4, 5, 6]); set1.union(set4); // => Set { 1, 2, 3, 4, 5, 6 } - // 5. 取交集 + // 5. intersection set1.intersect(set4); // => Set { 2, 3 } - // 6. 取差集 + // 6. except set1.subtract(set4); // => Set { 1 } ``` -## ImmutableJS 的特性整理 +## ImmutableJS characteristics summary 1. Persistent Data Structure - 在 `ImmutableJS` 的世界裡,只要資料一被創建,就不能修改,維持 `Immutable`。就不會發生下列的狀況: + In the world of `ImmutableJS`, once data is created it cannot be mutated, it remains `Immutable`. This prevents the below situation: ```javascript var obj = { @@ -145,13 +145,13 @@ ImmutableJS 提供了 7 種不可修改的資料類型:`List`、`Map`、`Stack }; funcationA(obj); - console.log(obj.a) // 不確定结果為多少? + console.log(obj.a) // unsure of the resulting value ``` - 使用 `ImmutableJS` 就沒有這個問題: + Using `ImmutableJS` eliminates this problem: ```javascript - // 有些開發者在使用時會在 ``Immutable` 變數前加 `$` 以示區隔。 + // some developers will prepend a `$` before `Immutable` variables to allow differentiation. const $obj = fromJS({ a: 1 @@ -162,7 +162,7 @@ ImmutableJS 提供了 7 種不可修改的資料類型:`List`、`Map`、`Stack ``` 2. Structural Sharing - 為了維持資料的不可變,又要避免像 `deepCopy` 一樣複製所有的節點資料而造成的資源損耗,在 `ImmutableJS` 使用的是 Structural Sharing 特性,亦即如果物件樹中一個節點發生變化的話,只會修改這個節點和和受它影響的父節點,其他節點則共享。 + In order to maintain data structure immutability, as well as avoiding `deepCopy`-esque methods where every node's data is copied and creates a waste of resources, `ImmutableJS` makes use of Structural Sharing, meaning if a node within the object tree is altered, only that node and the parent nodes affected by it are edited, everything else is shared. ```javascript const obj = { @@ -189,36 +189,36 @@ ImmutableJS 提供了 7 種不可修改的資料類型:`List`、`Map`、`Stack // -3 ``` -4. 豐富的 API 並提供快速轉換原生 JavaScript 的方式 - 在 ImmutableJS 中可以使用 `fromJS()`、`toJS()` 進行 JavaScript 和 ImmutableJS 之間的轉換。但由於在轉換之間會非常耗費資源,所以若是你決定引入 `ImmutableJS` 的話請盡量維持資料處在 `Immutable` 的狀態。 +4. A rich API that also provides a method for quick conversion to source JavaScript + In ImmutableJS `fromJS()` and `toJS()` may be used to commence conversion between JavaScript and ImmutableJS. But because the conversion process is resource intensive, if you have decided to import `ImmutableJS` please try to keep data structures in an `Immutable` state. -5. 支持 Functional Programming - `Immutable` 本身就是 Functional Programming(函數式程式設計)的概念,所以在 `ImmutableJS` 中可以使用許多 Functional Programming 的方法,例如:`map`、`filter`、`groupBy`、`reduce`、`find`、`findIndex` 等。 +5. Support of Functional Programming + `Immutable` inherently uses Functional Programming concepts, so in `ImmutableJS` many Functional Programming syntaxes are supported, for example: `map`, `filter`, `groupBy`, `reduce`, `find`, `findIndex` etc. -6. 容易實現 Redo/Undo 歷史回顧 +6. Easily Redo/Undo history -## React 效能優化 -`ImmutableJS` 除了可以和 `Flux/Redux` 整合外,也可以用於基本 react 效能優化。以下是一般使用效能優化的簡單方式: +## React performance optimization +`ImmutableJS` not only helps with `Flux/Redux` integration, it optimizes the performance of basic react. Below is an easy way to optimize React performance: -傳統 JavaScript 比較方式,若資料型態為 Primitive 就不會有問題: +Traditional JavaScript comparison method, if the data type is Primitive there will be no problem: ```javascript -// 在 shouldComponentUpdate 比較接下來的 props 是否一致,若相同則不重新渲染,提昇效能 +// shouldComponentUpdate compares the current prop with the next, if it is the same no re-rendering takes place, improving performance shouldComponentUpdate (nextProps) { return this.props.value !== nextProps.value; } ``` -但當比較的是物件的話就會出現問題: +If objects are being compared there will be a problem: ```javascript -// 假設 this.props.value 為 { foo: 'app' } -// 假設 nextProps.value 為 { foo: 'app' }, -// 雖然兩者值是一樣,但由於 reference 位置不同,所以視為不同。但由於值一樣應該要避免重複渲染 +// Let this.props.value be { foo: 'app' } +// Let nextProps.value be { foo: 'app' }, +// Although both have the same value, but as the referenced location is different, they count as being different. But because the values are identical re-rendering should be avoided this.props.value !== nextProps.value; // true ``` -使用 `ImmutableJS`: +Using `ImmutableJS`: ```javascript var SomeRecord = Immutable.Record({ foo: null }); @@ -227,7 +227,7 @@ var y = x.set('foo', 'azz'); x === y; // false ``` -在 ES6 中可以使用官方文件上的 `PureRenderMixin` 進行比較,可以讓程式碼更簡潔: +In ES6 we can use the officially provided `PureRenderMixin` for comparison operations, resulting in cleaner code: ```javascript import PureRenderMixin from 'react-addons-pure-render-mixin'; @@ -242,23 +242,23 @@ class FooComponent extends React.Component { } ``` -## 總結 -雖然 `ImmutableJS` 的引入可以帶來許多好處和效能的提升但由於引入整體檔案較大且較具侵入性,在引入之前可以自行評估看看是否合適於目前的專案。接下來我們將在後面的章節講解如何將 `ImmutableJS` 和 `Redux` 整合應用到實務上的範例。 +## Summary +Although `ImmutableJS` importation can bring many benefits and increases in performance, the overall file size becomes larger and it is invasive, one should evaluate the suitability for the current project prior to importing. In the next chapter we will discuss how to integrate `ImmutableJS` with `Redux` using a practical application as example. -## 延伸閱讀 -1. [官方網站](https://facebook.github.io/immutable-js/) -2. [Immutable.js初识](http://www.w3cplus.com/javascript/immutable-js.html) -3. [Immutable 详解及 React 中实践](https://github.com/camsong/blog/issues/3) -4. [为什么需要Immutable.js](http://zhenhua-lee.github.io/react/Immutable.html) -5. [facebook immutable.js 意义何在,使用场景?](https://www.zhihu.com/question/28016223) -6. [React 巢狀 Component 效能優化](https://blog.wuct.me/react-%E5%B7%A2%E7%8B%80-component-%E6%95%88%E8%83%BD%E5%84%AA%E5%8C%96-b01d8a0d3eff#.3kf4h1xq1) +## Extended Reading +1. [Official Website](https://facebook.github.io/immutable-js/) +2. [Immutable.js Initial Knowledge](http://www.w3cplus.com/javascript/immutable-js.html) +3. [Immutable in-depth explanation and putting to practice in React](https://github.com/camsong/blog/issues/3) +4. [Why is Immutable.js needed](http://zhenhua-lee.github.io/react/Immutable.html) +5. [facebook immutable.js purpose, when to use?](https://www.zhihu.com/question/28016223) +6. [React nested Component performance optimization](https://blog.wuct.me/react-%E5%B7%A2%E7%8B%80-component-%E6%95%88%E8%83%BD%E5%84%AA%E5%8C%96-b01d8a0d3eff#.3kf4h1xq1) 7. [PureRenderMixin](https://facebook.github.io/react/docs/pure-render-mixin.html) 8. [seamless-immutable](https://github.com/rtfeldman/seamless-immutable) 9. [Immutable Data Structures and JavaScript](http://jlongster.com/Using-Immutable-Data-Structures-in-JavaScript) -(image via [risingstack](https://risingstack-blog.s3.amazonaws.com/2016/Jan/immutable_logo_for_react_js_best_practices-1453211749818.png)) +(image via [risingstack](https://risingstack-blog.s3.amazonaws.com/2016/Jan/immutable_logo_for_react_js_best_practices-1453211749818.png)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:React Router 入門實戰教學](https://github.com/kdchang/reactjs101/blob/master/Ch05/react-router-introduction.md) | [下一章:Flux 基礎概念與實戰入門](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-flux-introduction.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Learn by Writing: React Router Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch05/react-router-introduction.md) | [Next article: Flux Basic Concept and Putting to Practice](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-flux-introduction.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch07/README.md b/Ch07/README.md index 380f794..f86670f 100644 --- a/Ch07/README.md +++ b/Ch07/README.md @@ -1,8 +1,8 @@ # Ch07 Flux/Redux -1. [Flux 基礎概念與實戰入門](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-flux-introduction.md) -2. [Redux 基礎概念](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-redux-introduction.md) -3. [Redux 實戰入門](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-redux-real-world-example.md) +1. [Flux Basic Concept and Putting to Practice](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-flux-introduction.md) +2. [Redux Basic Concept](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-redux-introduction.md) +3. [Redux Real World Example](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-redux-real-world-example.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch07/react-flux-introduction.md b/Ch07/react-flux-introduction.md index 7757aa3..05a442e 100644 --- a/Ch07/react-flux-introduction.md +++ b/Ch07/react-flux-introduction.md @@ -1,22 +1,22 @@ -# Flux 基礎概念與實戰入門 +# Flux Basic Concept and Putting to Practice ![React Flux](./images/react-flux.jpeg "React Flux") -## 前言 -隨著 React App 複雜度提昇,我們會發現常常需要從 Parent Component 透過 props 傳遞方法到 Child Component 去改變 state tree,不但不方便也難以管理,因此我們需要更好的資料架構來建置更複雜的應用程式。[Flux](https://facebook.github.io/flux/) 是 Facebook 推出的 client-side 應用程式架構(Architecture),主要想解決 `MVC` 架構的一些問題。事實上,Flux 並非一個完整的前端 Framework,其特色在於實現了 Unidirectional Data Flow(單向流)的資料流設計模式,在開發複雜的大型應用程式時可以更容易地管理 state(狀態)。由於 React 主要是負責 View 的部份,所以透過搭配 Flux-like 的資料處理架構,可以更好的去管理我們的 state(狀態),處理複雜的使用者互動(例如:Facebook 同時要維護使用者是否按讚、點擊相片,是否有新訊息等狀態)。 +## Foreword +As the complexity of React App increases, we discover we frequently change the state tree through sending props from Parent Components to Child Components, but not only is this inconvenient it is also hard to manage, therefore we need a better data structure to build more complex applications. [Flux](https://facebook.github.io/flux/) is a client-side Architecture application released by Facebook, aimed to resolve some problems associated with `MVC` structuring. In truth, Flux is not a complete front-end Framework, its distinguishing feature is that it achieve Unidirectional Data Flow design, allowing easier state management when developing large intricate applications. Because React mainly handles the View portion, paired with a Flux-like data managment architecture, we can more easily manage our state, and process more complicated user interaction (For example: Facebook must simultaneously maintain the states of whether the user has hit "like", clicked the photo, or has a new message). -由於原始的 Flux 架構在實現上有些部分可以精簡和改善,在實務上我們通常會使用開發者社群開發的 Flux-like 相關的架構實現(例如:[Redux](http://redux.js.org/index.html)、[Alt](http://alt.js.org/)、[Reflux](https://github.com/reflux/refluxjs) 等)。不過這邊我們主要會使用 Facebook 本身提供 `Dispatcher API` 函式庫(可以想成是一個 pub/sub 處理器,透過 broadcast 將 `payloads` 傳給註冊的 callback function)並搭配 `NodeJS` 的 `EventEmitter` 模組去完成 Flux 架構的實現。 +Because the original Flux architecture needs some streamlining and improvements, in practice we usually use a Flux-like architecture written by the developer community (Such as: [Redux](http://redux.js.org/index.html), [Alt](http://alt.js.org/), [Reflux](https://github.com/reflux/refluxjs) etc). Here we will mainly use the Facebook-provided `Dispatcher API` library (which can be thought of as a pub/sub handler, utilizes broadcast to pass `payloads` to the registered callback function) paired with `NodeJS`' `EventEmitter` module to complete our Flux architecture. -## Flux 概念介紹 +## Flux Concept Introduction ![React Flux](./images/flux-simple-diagram.png "React Flux") -在 Flux Unidirectional Data Flow(單項流)世界裡有四大主角,分別負責不同對應的工作: +In the Unidirectional Data Flow World of Flux there are four major actors, each responsible for different tasks: 1. actions / Action Creator - action 負責定義所有改變 state(狀態)的行為,可以讓開發者快速了解 App 的各種功能,若你想改變 state 你只能發 action。注意 action 可以是同步或是非同步。例如:新增代辦事項,呼叫非同步 API 獲取資料。 + action is responsible for defining the behaviour of all changed states, in order to allow the developer to quickly understand all the features of the App, if you want to change state you may only send out actions. Pay note that action may be synchronous or asynchronous. For example: adding a new task, we call the asynchronous API to acquire data. - 實務上我們會分成 action 和 Action Creator。action 為描述行為的 object(物件),Action Creator 將 action 送給 dispatcher。一般來說符合 Flux Standard Action 的 action 會如以下範例程式碼,具備 `type` 來區別所觸發的行為。而 `payload` 則是所夾帶的資料: + In practice we differentiate action from Action Creator. Action is the object that describes a behaviour, Action Creator hands the action over to the dispatcher. Normally an action that is a Flux Standard Action would be written like the below example code, using `type` to differentiate the triggering behaviour, `payload` is the data carried with it: ``` // action @@ -30,7 +30,7 @@ AppDispatcher.dispatch(addTodo); ``` - 當發生 rejected Promise 情況: + when a Promise is rejected: ``` { @@ -42,44 +42,44 @@ 2. Dispatcher - `Dispatcher` 是 Flux 架構的核心,每個 App 只有一個 Dispatcher,提供 API 讓 store 可以註冊 `callback function`,並負責向所有 store 發送 action 事件。在本範例中我們使用 Facebook 提供的 Dispatcher API,其內建有 `dispatch` 和 `subscribe` 方法。 + `Dispatcher` is the core of Flux architecture, every App has only one Dispatcher, allowing the API to store registerable `callback function`s, as well as sending all action events to the store. In this demo we use the Facebook-provided Dispatcher API, which comes with the `dispatch` and `subscribe` methods. 3. Stores - 一個 App 通常會有多個 store 負責存放業務邏輯,根據不同業務會有不同 store,例如:TodoStore、RecipeStore。 store 負責操作和儲存資料並提供 `view` 使用 `listener`(監聽器),若有資料更新即會觸發更新。值得注意的是 store 只提供 `getter API` 讀取資料,若想改變 state 一律發送 action。 + An App typically has multiple stores where logical operations are contained, differing operations use different stores, for example: TodoStore, RecipeStore. A store is responsible for operating and saving information and providing `listener`s for the `view` to use, if the data is changed it will trigger an update. It is worth noting that store only allows `getter API` to read its data, if a change in state is desired an action must be sent. -4. Views(Controller Views) +4. Views (Controller Views) - 這部份是 `React` 負責的範疇,負責提供監聽事件的 `callback function`,當事件發生時重新取得資料並重繪 `View`。 + This is the category `React` is responsible for, it handles event-listening `callback function`s, when events take place data retrieval takes place and the `View` is redrawn. -## Flux 流程回顧 +## Flux workflow review ![React Flux](./images/flux-react.png "React Flux") -Flux 架構前置作業: +Flux architecture for front-end processing: -1. Stores 向 Dispatcher 註冊 callback,當資料改變時告知 Stores -2. Controller Views 向 Stores 取得初始資料 -3. Controller Views 將資料給 Views 去渲染 UI -4. Controller Views 向 store 註冊 listener,當資料改變時告知 Controller Views +1. Stores register callbacks with the Dispatcher, Stores are notified when there is a change in data +2. Controller Views access initial data from Stores +3. Controller Views hands data to Views for UI rendering +4. Controller Views registers listeners with stores, when information is altered Controller Views are notified -Flux 與使用者互動運作流程: +Flux user interaction workflow: -1. 使用者和 App 互動,觸發事件,Action Creator 發送 actions 給 Dispatcher -2. Dispatcher 依序將 action 傳給 store 並由 action type 判斷合適的處理方式 -3. 若有資料更新則會觸發 Controller Views 向 store 註冊的 listener 並向 store 取得更新資料 -4. View 根據 Controller Views 的新資料重新繪製 UI +1. User interacts with App, triggering an event, the Action Creator sends actions to the Dispatcher +2. The Dispatcher sequentially transfers action to stores and discerns appropriate handling based on action type +3. If information is altered, the listeners registered within the stores by the Controller Views now retrieve the updated information from the store +4. View redraws the UI according to the new information in Controller Views -## Flux 實戰初體驗 -介紹完了整個 Flux 基本架構後,接下來我們就來動手實作一個簡單 Flux 架構的 Todo,讓使用者可以在 `input` 輸入代辦事項並新增。 +## Flux Putting to Practice First Experience +After introducing the basics of Flux architecture, we will now put our knowledge to practice with a simple Todo app using Flux architecture, allowing users to `input` and create todo items. -首先,我們先完成一些開發的前置作業,先透過以下指令在根目錄產生 npm 設定檔 `package.json`: +First, we will do some initial development setup, using the below command to create npm settings file `package.json` in the project root directory: ``` $ npm init ``` -安裝相關套件(包含開發環境使用的套件): +Install related plugins (including development environment plugins): ``` $ npm install --save react react-dom flux events @@ -89,11 +89,11 @@ $ npm install --save react react-dom flux events $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es2015 babel-preset-react eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react html-webpack-plugin webpack webpack-dev-server ``` -安裝好後我們可以設計一下我們的資料夾結構,首先我們在根目錄建立 `src`,放置 `script` 的 `source` 。在 `components` 資料夾中我們會放置所有 `components`(個別元件資料夾中會用 `index.js` 輸出元件,讓引入元件更簡潔),另外還有 `actions`、`constants`、`dispatcher`、`stores`,其餘設定檔則放置於根目錄下。 +After installation we can design our folder structure, first we create `src` in root project directory to place our `source` files for `script`. In `components` directory we place all `components` (individual component folders will use `index.js` to export components, allowing for cleaner component imports), additionally there are `actions`, `constants`, `dispatcher`, `stores`, the remaining configuration files are placed under the root directory. -![React Flux 資料夾結構](./images/folder.png "React Flux 資料夾結構") +![React Flux directory structure](./images/folder.png "React Flux directory structure") -接下來我們參考上一章設定一下開發文檔(`.babelrc`、`.eslintrc`、`webpack.config.js`)。這樣我們就完成了開發環境的設定可以開始動手實作 `React Flux` 應用程式了! +Now we will reference the settings from last chapter to configurate our (`.babelrc`, `.eslintrc`, `webpack.config.js`). This concludes our development environment setup and allows us to start putting our knowledge to practice by building a `React Flux` application! HTML Markup: @@ -110,7 +110,7 @@ HTML Markup: ``` -以下為 `src/index.js` 完整程式碼,安排了父 `component` 和在 HTML Markup 插入位置: +Below is the full code for `src/index.js`, where we set up a parent `component` and the insertion point for our HTML Markup: ```javascript import React from 'react'; @@ -136,13 +136,13 @@ class App extends React.Component { ReactDOM.render(, document.getElementById('app')); ``` -通常實務上我們會開一個 `constants` 資料夾存放 `config` 或是 `actionTypes` 常數。以下是 `src/constants/actionTypes.js`: +Usually in practice we will create a `constants` directory to place our `config` or `actionTypes` constants. Below is `src/constants/actionTypes.js`: ```javascript export const ADD_TODO = 'ADD_TODO'; ``` -在這個範例中我們繼承了 Facebook 提供的 Dispatcher API(主要是繼承了 `dispatch`、`register` 和 `subscribe` 的方法),打造自己的 DispatcherClass,當使用者觸發 `handleAction()` 會 `dispatch` 出事件。以下是 `src/dispatch/AppDispatcher.js`: +In this example we inherited the Facebook-provided Dispatcher API (mainly we inherited `dispatch`, `register` and `subscribe` methods), to create our own DispatcherClass, when the user triggers `handleAction()` our event will be `dispatch`ed. Below is `src/dispatch/AppDispatcher.js`: ```javascript // Todo app dispatcher with actions responding to both @@ -163,7 +163,7 @@ const AppDispatcher = new DispatcherClass(); export default AppDispatcher; ``` -以下是我們利用 `AppDispatcher` 打造的 `Action Creator` 由 `handleAction` 負責發出傳入的 `action` ,完整程式碼如 `src/actions/todoActions.js`: +Below we use the `Action Creator` made by `AppDispatcher`, using `handleAction` to manage imported `action`s, the full code for `src/actions/todoActions.js`: ```javascript import AppDispatcher from '../dispatcher/AppDispatcher'; @@ -181,7 +181,7 @@ export const TodoActions = { }; ``` -`Store` 主要是負責資料以及業務邏輯處理,我們繼承了 `events` 模組的 `EventEmitter`,當 `action` 傳入 `AppDispatcher.register` 的處理範圍後,根據 `action type` 選擇適合處理的 `store` 進行處理,處理完後透過 `emit` 方法發出事件讓監聽的 `Views Controller` 知道。以下是 `src/stores/TodoStore.js`: +`Store` is mainly responsible for information and operation handling, we inherited `EventEmitter` from `events` module, when an `action` is fed into `AppDispatcher.register`'s scope, an appropriate `store` is chosen for processing based on `action type`, on completion the `emit` method emits our event to the listening `Views Controller`. Below is `src/stores/TodoStore.js`: ```javascript import AppDispatcher from '../dispatcher/AppDispatcher'; @@ -222,7 +222,7 @@ AppDispatcher.register((action) => { export default TodoStore; ``` -在這個 React Flux 範例中我們把 `View` 和 `Views Controller` 整合在一起。在 `TodoHeader` 中,我們主要任務是讓使用者可以透過 `input` 新增代辦事項。使用者輸入文字在 `input` 時會觸發 `onChange` 事件,進而更新內部的 `state`,當使用者按了送出鈕就會觸發 `onAdd` 事件,`dispatch` 出 `addTodo event`。以下是 `src/components/TodoHeader.js` 完整範例: +In thie React Flux demo we integrated `View` with `Views Controller`. In `TodoHeader`, our main mission is to allow users to add new todo tasks via `input`. When the user enters words within `input`, `onChange` event is triggered, updating internal `state`, when the user clicks the submit button `onAdd` event is triggered, and the `addTodo event` is `dispatch`ed. Below is the full demo for `src/components/TodoHeader.js`: ```javascript import React, { Component } from 'react'; @@ -257,13 +257,13 @@ class TodoHeader extends Component {
    @@ -274,7 +274,7 @@ class TodoHeader extends Component { export default TodoHeader; ``` -在上面的 Component 中我們讓使用者可以新增代辦事項,接下來我們要讓新增的代辦事項可以顯示。我們在 `componentDidMount` 設了一個監聽器 `TodoStore` 資料改變時會去把資料重新再更新,這樣當使用者新增代辦事項時 `TodoList` 就會保持同步。當以下是 `src/components/TodoList.js` 完整程式碼: +Above our Component allows users to add new todo tasks, now we will allow the newly added todo task to be displayed. We added a listener `TodoStore` in `componentDidMount` which will update the data when it is changed, this allows the user-submitted todo task to be in sync with `TodoList`. Below is the full code for `src/components/TodoList.js`: ```javascript import React, { Component } from 'react'; @@ -317,37 +317,37 @@ class TodoList extends Component { export default TodoList; ``` -若讀者都有跟著上面的步驟走完的話,最後我們在終端機的根目錄位置執行 `npm start` 就可以看到整個成果囉,YA! +If the reader was able to follow the steps above, in the end we can run `npm start` within the terminal from the root directory, and see the entire result, YA! ![React Flux ](./images/flux-demo.png "React Flux ") -## 總結 -Flux 優勢: +## Summary +Flux advantages: -1. 讓開發者可以快速了解整個 App 中的行為 -2. 資料和業務邏輯統一存放好管理 -3. 讓 View 單純化只負責 UI 的排版不需負責 state 管理 -4. 清楚的架構和分工對於複雜中大型應用程式易於維護和管理程式碼 +1. Allows the developer to quickly understand the behaviours of the entire App +2. Data and operational logic are placed together permitting ease of management +3. Simplifies View's role to only be responsible for setting the UI and no state management +4. A clear structure and division of work promotes maintainence of code in intricate mid to large sized applications -Flux 劣勢: +Flux disadvantages: -1. 程式碼上不夠簡潔 -2. 對於簡單小應用來說稍微複雜 +1. The code is not sleek +2. The code is rather too complicated for a simple smaller application -以上就是 Flux 的實戰入門,我知道一開始接觸 Flux 的讀者一定會覺得很抽象,有些讀者甚至會覺得這個架構到底有什麼好處(明明感覺沒比 MVC 高明到哪去或是一點都不簡潔),但如同上述優點所說 Flux 設計模式的優勢在於清楚的架構和分工對於複雜中大型應用程式易於維護和管理程式碼。若還是不熟悉的讀者可以跟著範例多動手,相信慢慢就可以體會 Flux 的特色。事實上,在開發社群中為了讓 Flux 架構更加簡潔,產生了許多 Flux-like 的架構和函式庫,接下來將帶讀者們進入目前最熱門的架構:`Redux`。 +Above is our Flux Putting to Practice Introduction, I know readers initially interacting with Flux may find it to be very abstract, and some readers may even doubt the usefulness of such an architecture (evidently it is not that much more brilliant than MVC and it is not remotely clean), but as listed under the advantages of Flux the design pattern excels at providing a clear structure and division of work which is easier to manage for mid to large sized codebases. Those readers who still feel unfamiliar can follow the demonstration by going through the motions, I believe gradually the unique qualities of Flux will be felt. In truth, the development community has created many Flux-like architectures and libraries to allow for a cleaner Flux architecture, in the following part I will introduce readers to the currently hottest one: `Redux`. -## 延伸閱讀 +## Extended Reading 1. [Getting To Know Flux, the React.js Architecture](https://scotch.io/tutorials/getting-to-know-flux-the-react-js-architecture) -2. [Flux 官方網站](https://facebook.github.io/flux/) -3. [從 Flux 與 MVC 的差異來簡介 Flux](http://blog.techbridge.cc/2016/04/29/introduce-flux-from-flux-and-mvc/) +2. [Flux Offical Website](https://facebook.github.io/flux/) +3. [Using the difference between Flux and MVC to introduce Flux](http://blog.techbridge.cc/2016/04/29/introduce-flux-from-flux-and-mvc/) 4. [Flux Stores and ES6](https://medium.com/@softwarecf/flux-stores-and-es6-9b453dbf9db#.uuf1ddj8u) 5. [React and Flux: Migrating to ES6 with Babel and ESLint](https://medium.com/front-end-developers/react-and-flux-migrating-to-es6-with-babel-and-eslint-6390cf4fd878#.vafamphwy) 6. [Building an ES6/JSX/React Flux App – Part 2 – The Flux](https://shellmonger.com/2015/08/17/building-an-es6jsxreact-flux-app-part-2-the-flux/) 7. [Question: How to choose between Redux's store and React's state? #1287](https://github.com/reactjs/redux/issues/1287) 8. [acdlite/flux-standard-action](https://github.com/acdlite/flux-standard-action) -(image via [devjournal](http://devjournal.ru/wp-content/uploads/2016/03/React.js-Flux-Redux.png)、[facebook](https://facebook.github.io/flux/)、[scotch.io](https://cask.scotch.io/2014/10/V70cSEC.png)) +(image via [devjournal](http://devjournal.ru/wp-content/uploads/2016/03/React.js-Flux-Redux.png), [facebook](https://facebook.github.io/flux/), [scotch.io](https://cask.scotch.io/2014/10/V70cSEC.png)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:ImmutableJS 入門教學](https://github.com/kdchang/reactjs101/blob/master/Ch06/react-immutable-introduction.md) | [下一章:Redux 基礎概念](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-redux-introduction.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: ImmutableJS Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch06/react-immutable-introduction.md) | [Next article: Redux Basic Concept](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-redux-introduction.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch07/react-redux-introduction.md b/Ch07/react-redux-introduction.md index 5622700..ce05a5d 100644 --- a/Ch07/react-redux-introduction.md +++ b/Ch07/react-redux-introduction.md @@ -1,24 +1,24 @@ -# Redux 基礎概念 +# Redux Basic Concept ![React Redux](./images/redux-logo.png "React Redux") -## 前言 -前面一個章節我們講解了 Flux 的功能和用法,但在實務上許多開發者較偏好的是同為 Flux-like 但較為簡潔且文件豐富清楚的 [Redux](http://redux.js.org/index.html) 當作狀態資料管理的架構。Redux 是由 Dan Abramov 所發起的一個開源的 library,其主要功能如官方首頁寫著:`Redux is a predictable state container for JavaScript apps.`,亦即 Redux 希望能提供一個可以預測的 state 管理容器,讓開發者可以可以更容易開發複雜的 JavaScript 應用程式(注意 Redux 和 React 並無相依性,只是和 React 可以有很好的整合)。 +## Foreword +In the previous chapter we discussed the features and usage of Flux, but in practice many developers prefer a similarly Flux-like but cleaner, file-rich and clear [Redux](http://redux.js.org/index.html) for their data management architecture. Redux is an open-source library released by Dan Abramov, its main functionality is as per the official homepage: `Redux is a predictable state container for JavaScript apps.`, allowing developers to easily develop complex JavaScript applications (note that Redux and React have no dependency upon each other, but integrates with React well). -## Flux/Redux 超級比一比 +## Flux/Redux Super Comparison -從簡單 Flux/Redux 比較圖可以看出兩者之間有些差異: +From a simple Flux/Redux comparison image we can see the two differ silightly: ![React Redux](./images/using-redux-compare.jpg "React Redux") -在開始實作 Redux App 之前我們先來了解一下 Redux 和 Flux 的一些差異: +Before we start to write our Redux App we should first understand some differences between Redux and Flux: -1. 只使用一個 store 將整個應用程式的狀態 (state) 用物件樹 (object tree) 的方式儲存起來: +1. Uses a single store to manage the entire application's state and object tree methods: - 原生的 Flux 會有許多分散的 store 儲存各個不同的狀態,但在 redux 中,只會有唯一一個 store 將所有的資料用物件的方式包起來。 + The original Flux uses many dispersed stores to save varying states, but in redux, only one store is used to save all data access objects. ```javascript - //原生 Flux 的 store + //original Flux store const userStore = { name: '' } @@ -26,7 +26,7 @@ text: '' } - // Redux 的單一 store + // Redux single store const state = { userState: { name: '' @@ -37,26 +37,26 @@ } ``` -2. 唯一可以改變 state 的方法就是發送 action,這部份和 Flux 類似,但 Redux 並沒有像 Flux 設計有 Dispatcher。Redux 的 action 和 Flux 的 action 都是一個包含 `type` 和 `payload` 的物件。 +2. The only way to alter state is to send an action, this part is similar to Flux, however Redux unlike Flux has not designed a Dispatcher. Redux actions and Flux actions both are objects which contain `type` and `payload`. -3. Redux 擁有 Flux 所沒有的 Reducer。Reducer 根據 action 的 type 去執行對應的 state 做變化的函式叫做 Reducer。你可以使用 switch 或是使用函式 mapping 的方式去對應處理的方式。 +3. Redux unlike Flux, has a Reducer. Reducer is a function which changes state according to action and type. You can use switch or the a function mapping method to match up handling methods. -4. Redux 擁有許多方便好用的輔助測試工具(例如:[redux-devtools](https://github.com/gaearon/redux-devtools)、[react-transform-boilerplate](https://github.com/gaearon/react-transform-boilerplate)),方便測試和使用 `Hot Module Reload`。 +4. Redux has many convenient and useful supplemental testing tools (such as: [redux-devtools](https://github.com/gaearon/redux-devtools), [react-transform-boilerplate](https://github.com/gaearon/react-transform-boilerplate), and the easy to test and use `Hot Module Reload`. -## Redux 核心概念介紹 +## Redux core concept introduction ![React Redux](./images/redux-flowchart.png "React Redux") -從上述的圖中我們可以看到 Redux 資料流的模型大致上可以簡化成: `View -> Action -> (Middleware) -> Reducer`。當使用者和 View 互動時會觸發事件發出 Action,若有使用 Middleware 的話會在進入 Reducer 進行一些處理,當 Action 進到 Reducer 時,Reducer 會根據,action type 去 mapping 對應處理的動作,然後回傳回新的 state。View 則因為偵測到 state 更新而重繪頁面。在這個章節我們討論的是 synchronous(同步)的情形,asynchronous(非同步)的狀況會在接下來的章節進行討論。以下就用官方網站上的簡單範例來讓大家感受一下 Redux 的整個使用流程: +In the above image we can see that Redux's data flow model can be boiled down to: `View -> Action -> (Middleware) -> Reducer`. When users interact with the View, event triggers send out Action, if Middleware is used, it enters the Reducer for some handling, when Action enters the Reducer, the Reducer uses action type for mapping to the corresponding handling tasks, and then returns the new state. View then redraws the UI once it detects a change in state. In this chapter we discuss the synchronousscenario, we will discuss the asynchronous scenario in the next chapter. Below we follow a simple example from the official website to allow everyone to experience the entire workflow of Redux: ```javascript import { createStore } from 'redux'; /** - 下面是一個簡單的 reducers ,主要功能是針對傳進來的 action type 判斷並回傳新的 state - reducer 規格:(state, action) => newState - 一般而言 state 可以是 primitive、array 或 object 甚至是 ImmutableJS Data。但要留意的是不能修改到原來的 state , - 回傳的是新的 state。由於使用在 Redux 中使用 ImmutableJS 有許多好處,所以我們的範例 App 也會使用 ImmutableJS + Below is a simple example of reducers, its main purpose is to return an appropriate new state based on the incoming action type + reducer signature: (state, action) => newState + Usually the state may be primitive, array or object and possibly even ImmutableJS Data. But pay note we cannot edit the original state, + we return a new state. Because there are many benefits of using ImmutableJS in Redux, our example App will also use ImmutableJS */ function counter(state = 0, action) { switch (action.type) { @@ -69,16 +69,16 @@ function counter(state = 0, action) { } } -// 創建 Redux store 去存放 App 的所有 state -// store 的可用 API { subscribe, dispatch, getState } +// Create a Redux store to save all of our App's state +// store's API allows { subscribe, dispatch, getState } let store = createStore(counter); -// 可以使用 subscribe() 來訂閱 state 是否更新。但實務通常會使用 react-redux 來串連 React 和 Redux +// we can use subscribe() to subscribe for updates in state. But in practice react-redux is often used to bridge React and Redux store.subscribe(() => console.log(store.getState()); ); -// 若想改變 state ,一律發 action +// if changes in state, send out our action store.dispatch({ type: 'INCREMENT' }); // 1 store.dispatch({ type: 'INCREMENT' }); @@ -87,46 +87,46 @@ store.dispatch({ type: 'DECREMENT' }); // 1 ``` -## Redux API 入門 +## Redux API Introduction -1. createStore:`createStore(reducer, [preloadedState], [enhancer])` +1. createStore: `createStore(reducer, [preloadedState], [enhancer])` - 我們知道在 Redux 中只會有一個 store。在產生 store 時我們會使用 `createStore` 這個 API 來創建 store。第一個參數放入我們的 `reducer` 或是有多個 `reducers` combine(使用 `combineReducers`)在一起的 `rootReducers`。第二個參數我們會放入希望預先載入的 `state` 例如:user session 等。第三個參數通常會放入我們想要使用用來增強 Redux 功能的 `middlewares`,若有多個 `middlewares` 的話,通常會使用 `applyMiddleware` 來整合。 + We know within Redux there is only one store. We use `createStore` API to create that store. The first variable is our `reducer` or several `reducers` combined (using `combineReducers`) as `rootReducers`. The second variable is for the `state`s we wish to have preloaded for example: user session etc. The third variable is for any `middlewares` we want to use to enhance Redux, if there are multiple `middlewares`, it is usually integrated via `applyMiddleware`. 2. Store - 屬於 Store 的四個方法: + Four methods under Store: - getState() - dispatch(action) - subscribe(listener) - replaceReducer(nextReducer) - 關於 Store 重點是要知道 Redux 只有一個 Store 負責存放整個 App 的 State,而唯一能改變 State 的方法只有發送 action。 + The main point regarding Store is knowing that Redux has only one store for the entire App's State, the only way to change State is to dispatch actions. 3. combineReducers:`combineReducers(reducers)` - combineReducers 可以將多個 reducers 進行整合並回傳一個 Function,讓我們可以將 reducer 適度分割 + combineReducers allows the incorporation of many reducers, returning a Function,allowing us to have appropriate division of reducer 4. applyMiddleware:`applyMiddleware(...middlewares)` - 官方針對 Middleware 進行說明 + The offical explanation for Middleware > It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. - 若有 NodeJS 的經驗的讀者,對於 middleware 概念應該不陌生,讓開發者可以在 req 和 res 之間進行一些操作。在 Redux 中 Middleware 則是扮演 action 到達 reducer 前的第三方擴充。而 applyMiddleware 可以將多個 `middlewares` 整合並回傳一個 Function,便於使用。 + If the reader has experienced NodeJS, the concept of middleware should not be foreign, allowing the developer to execute some operations between req and res. In Redux, Middleware plays the role of a third enhancer before action reaches reducer, and the use of applyMiddleware allows the incorporation of mutliple `middlewares` and returns a Function, for convenient use. - 若是你要使用 asynchronous(非同步)的行為的話需要使用其中一種 middleware: [redux-thunk](https://github.com/gaearon/redux-thunk)、[redux-promise](https://github.com/acdlite/redux-promise) 或 [redux-promise-middleware](https://github.com/pburtchaell/redux-promise-middleware) ,這樣可以讓你在 actions 中 dispatch Promises 而非 function。asynchronous(非同步)運作方式就如同下圖所示: + If you are using asynchronous behaviour you need to use particular middleware: [redux-thunk](https://github.com/gaearon/redux-thunk), [redux-promise](https://github.com/acdlite/redux-promise) or [redux-promise-middleware](https://github.com/pburtchaell/redux-promise-middleware), this allows you to dispatch Promises instead of functions following actions. Asynchronous workflow is as the below image shows: ![React Redux](./images/react-redux-diagram.png "React Redux") 5. bindActionCreators:`bindActionCreators(actionCreators, dispatch)` - bindActionCreators 可以將 `actionCreators` 和 `dispatch` 綁定,並回傳一個 Function 或 Object,讓程式更簡潔。但若是使用 react-redux 可以用 `connect` 讓 dispatch 行為更容易管理 + bindActionCreators can bind `actionCreators` and `dispatch`, returning a Function or Object, allowing cleaner code. However using react-redux `connect` will allow for easier management of dispatch behaviour -6. compose:`compose(...functions)` +6. compose: `compose(...functions)` - compose 可以將 function 由右到左合併並回傳一個 Function,如官網範例所示: + compose can combine functions from right to left and return a Function, as the official webpage demo shows: ``` import { createStore, combineReducers, applyMiddleware, compose } from 'redux' @@ -143,19 +143,19 @@ store.dispatch({ type: 'DECREMENT' }); ) ``` -## 總結 -以上介紹了 Redux 的基礎概念,若是讀者覺得還是有點抽象的話也沒關係,在下一個章節我們將實際帶大家開發一個整合 `React`、`Redux` 和 `ImmutableJS` 的 TodoApp。 +## Summary +Above we introduced Redux basic concepts, if the reader feels it is still abstract that is okay, in the next chapter we will guide everyone to integrate in a practical way `React`, `Redux` and `ImmutableJS` in a TodoApp. -## 延伸閱讀 -1. [Redux 官方網站](http://redux.js.org/index.html) -2. [Redux架构实践——Single Source of Truth](http://react-china.org/t/redux-single-source-of-truth/5564) +## Extended Reading +1. [Redux Official Homepage](http://redux.js.org/index.html) +2. [Redux Architecture Putting to Practice——Single Source of Truth](http://react-china.org/t/redux-single-source-of-truth/5564) 3. [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) -4. [使用Redux管理你的React应用](https://github.com/matthew-sun/blog/issues/18) +4. [Using Redux to manage your React app](https://github.com/matthew-sun/blog/issues/18) 5. [Using redux](http://www.slideshare.net/JonasOhlsson/using-redux) -(image via [githubusercontent](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo-title-dark.png)、[makeitopen](http://makeitopen.com/static/images/redux_flowchart.png)、[css-tricks](https://css-tricks.com/wp-content/uploads/2016/03/redux-article-3-03.svg)、[tighten](https://blog.tighten.co/assets/img/react-redux-diagram.png)、[tryolabs](http://blog.tryolabs.com/wp-content/uploads/2016/04/redux-simple-f8-diagram.png)、[facebook](https://facebook.github.io/flux/img/flux-simple-f8-diagram-with-client-action-1300w.png)、[JonasOhlsson](http://www.slideshare.net/JonasOhlsson/using-redux)) +(image via [githubusercontent](https://raw.githubusercontent.com/reactjs/redux/master/logo/logo-title-dark.png), [makeitopen](http://makeitopen.com/static/images/redux_flowchart.png), [css-tricks](https://css-tricks.com/wp-content/uploads/2016/03/redux-article-3-03.svg), [tighten](https://blog.tighten.co/assets/img/react-redux-diagram.png), [tryolabs](http://blog.tryolabs.com/wp-content/uploads/2016/04/redux-simple-f8-diagram.png), [facebook](https://facebook.github.io/flux/img/flux-simple-f8-diagram-with-client-action-1300w.png), [JonasOhlsson](http://www.slideshare.net/JonasOhlsson/using-redux)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:Flux 基礎概念與實戰入門](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-flux-introduction.md) | [下一章:Redux 實戰入門](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-redux-real-world-example.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Flux Basic Concept and Putting to Practice](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-flux-introduction.md) | [Next article: Redux Real World Example](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-redux-real-world-example.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch07/react-redux-real-world-example.md b/Ch07/react-redux-real-world-example.md index 2b7c3b2..2fb40a9 100644 --- a/Ch07/react-redux-real-world-example.md +++ b/Ch07/react-redux-real-world-example.md @@ -1,20 +1,21 @@ -# Redux 實戰入門 +# Redux Real World Example -## 前言 -上一節我們了解了 Redux 基本的概念和特性後,本章我們要實際動手用 Redux、React Redux 結合 ImmutableJS 開發一個簡單的 Todo 應用。話不多說,那就讓讓我們開始吧! +## Foreword -以下這張圖表示了整個 React Redux App 的資料流程圖(使用者與 View 互動 => dispatch 出 Action => Reducers 依據 action tyoe 分配到對應處理方式,回傳新的 state => 透過 React Redux 傳送給 React,React 重新繪製 View): +After gaining understanding of the basic concepts of Redux through the previous article, we will now put our Redux knowledge to practice with React and Redux combined with ImmutableJS to create a simple Todo app. Let's cut to the chase and begin! + +Below is an image representing an entire React Redux App's data flow (users interact with View => dispatch Actions => Reducers match actions to appropriate methods based on action type => returns a new state => React-Redux sends the new state to React, which re-renders the View): ![React Redux](./images/redux-flow.png "React Redux") -## 動手創作 React Redux ImmutableJS TodoApp -在開始創作之前我們先完成一些開發的前置作業,先透過以下指令在根目錄產生 npm 設定檔 `package.json`: +## Hands-on creating React Redux ImmutableJS TodoApp +Before we begin we will complete some development housekeeping tasks, using the below command to create an npm settings file `package.json`: ``` $ npm init ``` -安裝相關套件(包含開發環境使用的套件): +Install related packages (including dev environment packages): ``` $ npm install --save react react-dom redux react-redux immutable redux-actions redux-immutable @@ -24,19 +25,19 @@ $ npm install --save react react-dom redux react-redux immutable redux-actions r $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es2015 babel-preset-react eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react html-webpack-plugin webpack webpack-dev-server ``` -安裝好後我們可以設計一下我們的資料夾結構,首先我們在根目錄建立 `src`,放置 `script` 的 `source` 。在 `components` 資料夾中我們會放置所有 `components`(個別元件資料夾中會用 `index.js` 輸出元件,讓引入元件更簡潔)、`containers`(負責和 store 互動取得 state),另外還有 `actions`、`constants`、`reducers`、`store`,其餘設定檔則放置於根目錄下。 +After installation we can first design our folder structure, first we will make a `src` folder in our project root directory, where we will place our `source` files for `script`s. In `components` directory we will place all of our `components` (individual component directories will use `index.js` to export components, allowing cleaner component importation), `containers` (responsible for interacting with store to get state), and also `actions`, `constants`, `reducers`, `store`, the other configuration files should be placed under project root directory. -大致上的資料夾結構會長這樣: +Approximate folder structure will look like this: ![React Redux](./images/redux-folder.png "React Redux") -接下來我們參考上一章設定一下開發文檔(`.babelrc`、`.eslintrc`、`webpack.config.js`)。這樣我們就完成了開發環境的設定可以開始動手實作 `React Redux` 應用程式了! +Next we will refer to the previous article's settings to configure our (`.babelrc`, `.eslintrc`, `webpack.config.js`). This concludes our development environment setup and allows us to start working on our `React Redux` application! -首先我們先用 Component 之眼感受一下我們應用程式,將它切成一個個 `Component`。在這邊我們設計一個主要的 `Main` 包含兩個子 Component:`TodoHeader`、`TodoList`。 +First we picture our application with our eye for Components, dividing our application to individual `Component`s. In our design we have a main `Main` component that contains two child Components: `TodoHeader`, `TodoList`. ![React Redux](./images/react-redux-demo.png "React Redux") -首先設計 HTML Markup: +First we design our HTML Markup: ```html @@ -51,15 +52,15 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ``` -在撰寫 `src/index.js` 之前,我們先說明整合 `react-redux` 的用法。從以下這張圖可以看到 `react-redux` 是 React 和 Redux 間的橋樑,使用 `Provider`、`connect` 去連結 `store` 和 React View。 +Before we write `src/index.js`, we should first explain how to integrate `react-redux`. In the below image we can see that `react-redux` is the bridge between React and Redux, using `Provider` and `connect` to link `store` and React View. ![React Redux](./images/using-redux.jpg "React Redux") -事實上,整合了 `react-redux` 後,我們的 React App 就可以解決傳統跨 Component 之前傳遞 state 的問題和困難。只要透過 `Provider` 就可以讓每個 React App 中的 `Component` 取用 store 中的 state,非常方便(接下來我們也會更詳細說明 Container/Component、`connect` 的用法)。 +Actually, after we integrate `react-redux`, our React App can overcome the traditional problems and difficulties encountered in sending state before switching Components. Through `Provider` we can allow every `Component` in our React App to fetch state from store, this is very convenient (next we will detail how to use the `connect` method for Containers/Components).。 ![React Redux](./images/redux-store.png "React Redux") -以下是 `src/index.js` 完整程式碼: +Below is the completed code for `src/index.js`: ```javascript import React from 'react'; @@ -76,12 +77,12 @@ ReactDOM.render( ); ``` -其中 `src/components/Main/Main.js` 是 Stateless Component,負責所有 View 的進入點。 +In particular `src/components/Main/Main.js` is a Stateless Component, responsible for the entrypoint of the entire View. ```javascript import React from 'react'; import ReactDOM from 'react-dom'; -import TodoHeaderContainer from '../../containers/TodoHeaderContainer'; +import TodoHeaderContiner from '../../containers/TodoHeaderContainer'; import TodoListContainer from '../../containers/TodoListContainer'; const Main = () => ( @@ -94,7 +95,7 @@ const Main = () => ( export default Main; ``` -接下來我們定義一下 `Actions` 的部份,由於是範例 App 所以相對簡單,這邊只定義一個 todoActions。在這邊我們使用了 [redux-actions](https://github.com/acdlite/redux-actions),它可以方便我們使用 Flux Standard Action 格式的 action。以下是 `src/actions/todoActions.js` 完整程式碼: +Next we will define our `Actions`, because the example App is relatively simple, here we only define a todoActions. Here we used [redux-actions](https://github.com/acdlite/redux-actions), which enables us to conveniently use Flux Standard Action formatting for action. Below is the completed code for `src/actions/todoActions.js`: ```javascript import { createAction } from 'redux-actions'; @@ -109,13 +110,13 @@ export const deleteTodo = createAction('DELETE_TODO'); export const changeText = createAction('CHANGE_TEXT'); ``` -我們在 `src/actions/index.js` 將所有 actions 輸出 +We export all actions in `src/actions/index.js` ```javascript export * from './todoActions'; ``` -另外我們把 constants 放到 `components` 資料夾中方便管理,以下是 `src/constants/actionTypes.js` 程式碼: +We also place constants in `components` directory for ease of management, below is the completed code for `src/constants/actionTypes.js`: ```javascript export const CREATE_TODO = 'CREATE_TODO'; @@ -123,7 +124,7 @@ export const DELETE_TODO = 'DELETE_TODO'; export const CHANGE_TEXT = 'CHANGE_TEXT'; /* -或是可以考慮使用 keyMirror,方便產生與 key 相同的常數 +alternatively consider using keyMirror, allows convenient creation of variables that have the same key import keyMirror from 'fbjs/lib/keyMirror'; export default keyMirror({ @@ -135,9 +136,9 @@ export default keyMirror({ */ ``` -設定 Actions 後我們來討論一下 Reducers 的部份。在討論 Reducers 之前我們先來設定一下我們的前端的資料結構,在這邊我們把所有資料結構(initialState)放到 `src/constants/models.js` 中。這邊特別注意的是由於 Redux 中有一個重要特性是 `State is read-only`,也就是說更新當 reducers 進到 action 只會回傳新的 state 不會更改到原有的 state。因此我們會在整個 Redux App 中使用 `ImmutableJS` 讓整個資料流維持在 `Immutable` 的狀態,也可以提昇程式開發上的效能和避免不可預期的副作用。 +After setting our Actions we should discuss our Reducers. Before we discuss our Reducers we should first configure our front-end data structure, here we put all data initialState within `src/constants/model.js`. Pay special attention that because Redux has a special characteristic where `State is read-only`, this means upon update when reducers enter action only new states will be returned and the original state will not be altered. Therefore in our entire Redux App we use `ImmutableJS` to allow the data flow to remain `Immutable`, which also increases our performance in app development as well as prevent unexpected side effects. -以下是 `src/constants/models.js` 完整程式碼,其設定了 TodoState 的資料結構並使用 `fromJS()` 轉成 `Immutable`: +Below is the complete code for `src/constants/models.js`, in particular we defined the TodoState data structure and used `fromJS()` to convert to `Immutable`: ```javascript import Immutable from 'immutable'; @@ -153,7 +154,7 @@ export const TodoState = Immutable.fromJS({ }); ``` -接下來我們要討論的是 Reducers 的部份,在 `todoReducers` 中我們會根據接收到的 action 進行 mapping 到對應的處理函式並傳入夾帶的 `payload` 資料(這邊我們使用 [redux-actions](https://github.com/acdlite/redux-actions) 來進行 mapping,使用上比傳統的 switch 更為簡潔)。Reducers 接收到 action 的處理方式為 `(initialState, action) => newState`,最終會回傳一個新的 state,而非更改原來的 state,所以這邊我們使用 `ImmutableJS`。 +Now we discuss our Reducers, `todoReducers` takes care of mapping actions to appropriate handling methods and bringing `payload` data (here we use [redux-actions](https://github.com/acdlite/redux-actions) for mapping, the usage is cleaner than traditional switch). Reducers' action handling method is `(initialState, action) => newState`, finally returning a new state, instead of changing the original state, so here we use `ImmutableJS`. ```javascript import { handleActions } from 'redux-actions'; @@ -192,9 +193,9 @@ export default handleActions({ }, UiState); ``` -雖然 Redux 本身僅會有一個 store,但 redux 本身有提供了 `combineReducers` 可以讓我們切割我們 state 方便維護和管理。實上,state 的規劃也是一們學問,通常需要不斷地實作和工作團隊討論才能找到比較好的方式。不過這邊要注意的是我們改使用了 `redux-immutable` 的 `combineReducers` 這樣可以確保我們的 state 維持在 `Immutable` 的狀態。 +Although Redux can only allows one store, it offers `combineReducers` to allow us to dissect our state for convenient maintenance. In truth, state formatting is its own body of knowledge, usually one must continuously experiment and discuss with one's development team to figure out better ways to do it. Here we should pay note that we switched to using `redux-immutable`'s `combineReducers` to guarantee our state remains `Immutable`. -由於 Redux 官方也沒有特別明確或嚴謹的規範。在一般情況我會將 reducers 分為 `data` 和單純和 UI 有關的 `ui` state。但由於這邊是比較簡單的例子,我們最終只使用到 `src/reducers/data/todoReducers.js`。 +Redux official documentation does not specify strict formats. Usually I would separate reducers to `data` versus purely UI related `ui` states. But because this is a more simple example, we end up only using `src/reducers/data/todoReducers.js`. ```javascript import { combineReducers } from 'redux-immutable'; @@ -208,7 +209,7 @@ const rootReducer = combineReducers({ export default rootReducer; ``` -還記得我們上面說明 React Redux 之前的橋樑時有提到的 store 嗎?現在我們要更仔細地去設計 `store`,我們這邊使用到了 redux 其中兩個 API:applyMiddleware、createStore。分別可以產生 store 和掛載我們要使用的 middleware(這邊我們只使用到 redux-logger 方便我們除錯)。注意我們 initialState 也是維持在 `Immutable` 的狀態。 +Remember above when we explained the bridge between React and Redux we mentioned the store? Now we will design our `store` in more detail, here we used two other APIs from redux: applyMiddleware and createStore. Respectively they can create store and mount our chosen middleware (here we only used redux-logger for convenience in debugging). Pay attention that our initialState is also `Immutable`. ```javascript import { createStore, applyMiddleware } from 'redux'; @@ -225,22 +226,22 @@ export default createStore( ); ``` -透過 `src/store/index.js` 輸出 configureStore: +Through `src/store/index.js` we export configureStore: ```javascript export { default } from './configureStore'; ``` -講解完架構層面的議題,終於我們來到了 View 的部份。加油,距離我們終點也不遠了! -在開始討論 `Component` 的部份之前我們先來研究一下 +After discussing our architecture, we finally come to the View part. You can do it, the end is in sight! +Before we discuss `Component`s let us study -[react-redux](https://github.com/reactjs/react-redux) 所提供的 API `connect` 將 props 傳給 Component,其用法如下: +The API `connect` provided by [react-redux](https://github.com/reactjs/react-redux) for sending props to Component, its usage is as below: `connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])` -在我們的範例 App 中我們只會先用到前兩個參數,第三個參數會在之後的例子裡用到。第一個參數 mapStateToProps 是一個讓開發者可以從 store 取出想要 state 並當做 props 往下傳的功能,第二個參數則是將 dispatch 行為封裝成函數順著 props 可以方便往下傳和呼叫。 +In our example App we will only use the first two parameters at first, the third parameter will be used in our later example. The first parameter mapStateToProps allows developers to extract states from store and map to props, the second parameter encapsulates dispatch behaviour as a function and sends to props allowing convenient mapping and calling. -以下是 `src/components/TodoHeader/TodoHeader.js` 的部份: +Below is our `src/components/TodoHeader/TodoHeader.js`: ```javascript import React from 'react'; @@ -248,23 +249,23 @@ import ReactDOM from 'react-dom'; import { connect } from 'react-redux'; import TodoHeader from '../../components/TodoHeader'; -// 將欲使用的 actions 引入 +// import our desired actions import { changeText, createTodo, } from '../../actions'; const mapStateToProps = (state) => ({ - // 從 store 取得 todo state + // get todo state from store todo: state.getIn(['todo', 'todo']) }); const mapDispatchToProps = (dispatch) => ({ - // 當使用者在 input 輸入資料值即會觸發這個函數,發出 changeText action 並附上使用者輸入內容 event.target.value + // when the user input information at the input element this function is triggered, sending out changeText action and provides the content entered by the user as event.target.value onChangeText: (event) => ( dispatch(changeText({ text: event.target.value })) ), - // 當使用者按下送出時,發出 createTodo action 並清空 input + // when the user presses send, the createTodo action is sent and clears input element onCreateTodo: () => { dispatch(createTodo()); dispatch(changeText({ text: '' })); @@ -276,7 +277,7 @@ export default connect( mapDispatchToProps, )(TodoHeader); -// 開始建設 Component 並使用 connect 進來的 props 並綁定事件(onChange、onClick)。注意我們的 state 因為是使用 `ImmutableJS` 所以要用 `get()` 取值 +// Constructing Component and using props from connect to bind events (onChange, onClick). Pay attention that because we used `ImmutableJS` our state uses `get()` to get value const TodoHeader = ({ onChangeText, onCreateTodo, @@ -285,14 +286,14 @@ const TodoHeader = ({

    TodoHeader

    - +
    ); export default TodoHeader; ``` -以下是 `src/components/TodoList/TodoList.js` 的部份: +Below is `src/components/TodoList/TodoList.js` portion: ```javascript import React from 'react'; @@ -308,7 +309,7 @@ const mapStateToProps = (state) => ({ todos: state.getIn(['todo', 'todos']) }); -// 由 Component 傳進欲刪除元素的 index +// Using Component to send in the index of the element we want to delete const mapDispatchToProps = (dispatch) => ({ onDeleteTodo: (index) => () => ( dispatch(deleteTodo({ index })) @@ -320,7 +321,7 @@ export default connect( mapDispatchToProps, )(TodoList); -// Component 部分值的注意的是 todos state 是透過 map function 去迭代出元素,由於要讓 React JSX 可以渲染並保持傳入觸發 event state 的 immutable,所以需使用 toJS() 轉換 component of array。 +// The value we need to pay attention to under Component is todos state which uses map function to iterate over elements, because we want React JSX to render and maintain the immutability of the incoming event state, we use toJS() to convert our array. const TodoList = ({ todos, onDeleteTodo, @@ -342,19 +343,19 @@ const TodoList = ({ export default TodoList; ``` -若是一切順利的話就可以在瀏覽器上看到自己努力的成果囉!(因為我們有使用 `redux-logger` 所以打開 console 會看到 action 和 state 的變化情形,但記得在 `production` 環境要拿掉) +If everything was successful we can see the results of our effort in the browser! (because we used `redux-logger` when we open the console we can see action and state changes, but remember to remove it for `production` environments) ![React Redux](./images/react-redux-dev-demo.png "React Redux") -## 總結 -以上就是 Redux 實戰入門,對於第一次自己動手寫 Redux 的朋友可能會需要多練習幾次,多體會整個架構。在接下來的章節我們將優化我們的 React Redux TodoApp,讓它可以有更清晰好維護的架構。 +## Summary +Above is my Redux Real World Example, those readers who are writing Redux for the first time may need to practice a few more times, to realize through experience the entire architecture. In the next chapter we will optimize our React Redux TodoApp, allowing it to have a more clean and easily maintainable structure. -## 延伸閱讀 -1. [Redux 官方網站](http://redux.js.org/index.html) +## Extended Reading +1. [Redux Official Homepage](http://redux.js.org/index.html) -(image via [JonasOhlsson](http://www.slideshare.net/JonasOhlsson/using-redux)、[licdn](https://media.licdn.com/mpr/mpr/shrinknp_800_800/AAEAAQAAAAAAAAUQAAAAJDAyMWU1MmZhLTYzMTQtNDJkNy1hYzM4LTE5MWQzNWM1ODcyNA.png)) +(image via [JonasOhlsson](http://www.slideshare.net/JonasOhlsson/using-redux), [licdn](https://media.licdn.com/mpr/mpr/shrinknp_800_800/AAEAAQAAAAAAAAUQAAAAJDAyMWU1MmZhLTYzMTQtNDJkNy1hYzM4LTE5MWQzNWM1ODcyNA.png)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:Redux 基礎概念](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-redux-real-world-example.md) | [下一章:Container 與 Presentational Components 入門](https://github.com/kdchang/reactjs101/blob/master/Ch08/container-presentational-component-.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Redux Basic Concept](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-redux-introduction.md) | [Next article: Container and Presentational Components Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch08/container-presentational-component-.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch08/README.md b/Ch08/README.md index 73964cc..6ca96bb 100644 --- a/Ch08/README.md +++ b/Ch08/README.md @@ -1,6 +1,6 @@ -# Ch08 Container 與 Presentational Components +# Ch08 Container and Presentational Components -1. [Container 與 Presentational Components 入門](https://github.com/kdchang/reactjs101/blob/master/Ch08/container-presentational-component-.md) +1. [Container and Presentational Components Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch08/container-presentational-component-.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch08/container-presentational-component-.md b/Ch08/container-presentational-component-.md index f6431d4..be99638 100644 --- a/Ch08/container-presentational-component-.md +++ b/Ch08/container-presentational-component-.md @@ -1,52 +1,52 @@ -# Container 與 Presentational Components 入門 +# Container and Presentational Components Introduction -## 前言 -在聊完了 React 和 Redux 整合後我們來談談分離 Presentational 和 Container Component 的概念,若你是第一次聽過這個名詞,我建議你可以先看看 Redux 作者 Dan AbramovFollow 所寫的這篇文章 [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.vtcuxsurv)。 +## Foreword +After chatting about React and Redux integration we now will discuss the concept of separating Presentational dn Container components, if this is the first time you have heard of this term, I recommend you can first take a look at author Dan Abramov's article [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.vtcuxsurv). -## Container 與 Presentational Components 超級比一比 -以下先參考 [Redux 官網](http://redux.js.org/docs/basics/UsageWithReact.html) 列出兩者相異之處: +## Container and Presentational Components Super Comparison +Below I referenced [Redux official documentation](http://redux.js.org/docs/basics/UsageWithReact.html) to list the difference between the two: 1. Presentational Components - - 用途:怎麼看事情(Markup、外觀) - - 是否讓 Redux 意識到:否 - - 取得資料方式:從 props 取得 - - 改變資料方式:從 props 去呼叫 callback function - - 寫入方式:手動處理 + - Purpose: how things look (Markup, aesthetics) + - Should let Redux be aware of it: no + - Method of data acquiry: via props + - Method to alter data: using props to call callback functions + - Entry method: manually 2. Container Components - - 用途:怎麼做事情(擷取資料,更新 State) - - 是否讓 Redux 意識到:是 - - 取得資料方式:訂閱 Redux State(store) - - 改變資料方式:Dispatch Redux Action - - 寫入方式:從 React Redux 產生 + - Purpose: how to do things (get data, update State) + - Should let Redux be aware of it: yes + - Method of data acquiry: subscribing to Redux State (store) + - Method to alter data: Dispatch Redux Action + - Entry method: created with React Redux - 從上面的分析讀者可以發現,兩者最大的差別在於 `Component` 主要負責單純的 UI 的渲染,而 `Container` 則負責和 Redux 的 store 溝通,作為 `Redux` 和 `Component` 之間的橋樑。這樣的分法可以讓程式架構和職責更清楚,所以接下來我們就使用上一章節的 Redux TodoApp 進行改造,改造成 Container 與 Presentational Components 模式。 + From the above analysis readers may discover, the biggest difference between the two is with presentational `Component`s are chiefly responsible for simple UI rendering, while `Container` components mainly handle communication between Redux and store, serving as the bridge between `Redux` and `Component`. This strategy allows code structure and division of labor to be more clear, so now we will use last article's Redux TodoApp for refactoring, to a Container and Presentational Components format. ## Container Components -以下是 `src/containers/TodoHeaderContainer/TodoHeaderContainer.js` 的部份: +Below is the `src/containers/TodoHeaderContainer/TodoHeaderContainer.js` portion: ```javascript import { connect } from 'react-redux'; import TodoHeader from '../../components/TodoHeader'; -// 將欲使用的 actions 引入 +// import desired actions import { changeText, createTodo, } from '../../actions'; const mapStateToProps = (state) => ({ - // 從 store 取得 todo state + // get todo state from store todo: state.getIn(['todo', 'todo']) }); const mapDispatchToProps = (dispatch) => ({ - // 當使用者在 input 輸入資料值即會觸發這個函數,發出 changeText action 並附上使用者輸入內容 event.target.value + // when user enters information to input, this function is triggered, sending out changeText action with user entered content as event.target.value onChangeText: (event) => ( dispatch(changeText({ text: event.target.value })) ), - // 當使用者按下送出時,發出 createTodo action 並清空 input + // when the user presses submit, sends out createTodo action and clears input onCreateTodo: () => { dispatch(createTodo()); dispatch(changeText({ text: '' })); @@ -59,7 +59,7 @@ export default connect( )(TodoHeader); ``` -以下是 `src/containers/TodoListContainer/TodoListContainer.js` 的部份: +Below is the `src/containers/TodoListContainer/TodoListContainer.js` portion: ```javascript import { connect } from 'react-redux'; @@ -87,13 +87,13 @@ export default connect( ## Presentational Components -以下是 `src/components/TodoHeader/TodoHeader.js` 的部份: +Below is the `src/components/TodoHeader/TodoHeader.js` portion: ```javascript import React from 'react'; import ReactDOM from 'react-dom'; -// 開始建設 Component 並使用 connect 進來的 props 並綁定事件(onChange、onClick)。注意我們的 state 因為是使用 `ImmutableJS` 所以要用 `get()` 取值 +// Begin to construct Component and use props sent in from connect to bind events (onChange, onClick). Pay note that because our state uses `ImmutableJS`, `get()` is used to extract values const TodoHeader = ({ onChangeText, @@ -103,21 +103,21 @@ const TodoHeader = ({

    TodoHeader

    - +
    ); export default TodoHeader; ``` -以下是 `src/components/TodoList/TodoList.js` 的部份: +Below is the `src/components/TodoList/TodoList.js` portion: ```javascript import React from 'react'; import ReactDOM from 'react-dom'; -// Component 部分值的注意的是 todos state 是透過 map function 去迭代出元素,由於要讓 React JSX 可以渲染並保持傳入觸發 event state 的 immutable,所以需使用 toJS() 轉換 component of array。 -// 由 Component 傳進欲刪除元素的 index +// The value we need to pay attention to under Component is todos state which uses map function to iterate over elements, because we want React JSX to render and maintain the immutability of the incoming event state, we use toJS() to convert our array. +// Using Component to send in the index of the element we want to delete const TodoList = ({ todos, @@ -140,16 +140,16 @@ const TodoList = ({ export default TodoList; ``` -## 總結 -That's it!透過區分 Container 與 Presentational Components 可以讓程式架構和職責更清楚了!接下來我們將運用我們所學實際開發兩個貼近生活的專案,讓讀者更加熟悉 React 生態系如何應用於實務上。 +## Summary +That's it!Through the distinction of Container from Presentational Components our code architecture and division of tasks is even clearer! Next we will use what we have learned to develop two projects useful in day to day life, allowing readers to become even more familiar with the practical applications of the React ecosystem. -## 延伸閱讀 +## Extended Reading 1. [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0#.vtcuxsurv) 2. [Redux Usage with React](http://redux.js.org/docs/basics/UsageWithReact.html) 3. [React Higher Order Components in depth](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.r8srulpaj) 4. [React higher order components](http://www.darul.io/post/2016-01-05_react-higher-order-components) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:Redux 實戰入門](https://github.com/kdchang/reactjs101/blob/master/Ch07/react-redux-real-world-example.md) | [下一章:用 React + Router + Redux + ImmutableJS 寫一個 Github 查詢應用](https://github.com/kdchang/reactjs101/blob/master/Ch09/react-router-redux-github-finder.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Redux Real World Example](https://github.com/sycherng/reactjs101/blob/en-US/Ch07/react-redux-real-world-example.md) | [Next article: using React + Router + Redux + ImmutableJS to write a Github search application](https://github.com/sycherng/reactjs101/blob/en-US/Ch09/react-router-redux-github-finder.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch09/README.md b/Ch09/README.md index 5339da2..48903ee 100644 --- a/Ch09/README.md +++ b/Ch09/README.md @@ -1,6 +1,6 @@ -# Ch09 用 React + Router + Redux + ImmutableJS 寫一個 Github 查詢應用 +# Learn by Writing: Using React + Router + Redux + ImmutableJS to write a Github search app -1. [用 React + Router + Redux + ImmutableJS 寫一個 Github 查詢應用](https://github.com/kdchang/reactjs101/blob/master/Ch09/react-router-redux-github-finder.md) +1. [Learn by Writing: Using React + Router + Redux + ImmutableJS to write a Github search app](https://github.com/sycherng/reactjs101/blob/en-US/Ch09/react-router-redux-github-finder.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch09/react-router-redux-github-finder.md b/Ch09/react-router-redux-github-finder.md index 464d8a2..d7e89c7 100644 --- a/Ch09/react-router-redux-github-finder.md +++ b/Ch09/react-router-redux-github-finder.md @@ -1,12 +1,12 @@ -# 用 React + Router + Redux + ImmutableJS 寫一個 Github 查詢應用 +# Learn by Writing: Using React + Router + Redux + ImmutableJS to write a Github search app -## 前言 -學了一身本領後,本章將帶大家完成一個單頁式應用程式(Single Page Application),整合 React + Redux + ImmutableJS + React Router 搭配 Github API 製作一個簡單的 Github 使用者查詢應用,實際體驗一下開發 React App 的感受。 +## Foreword +After learning many skills, this article will guide everyone in completing a Single Page Application, integrating React + Redux + ImmutableJS + React Router paired with Github API to create a simple Github user search app, to experience the feeling of developing a React App. -## 功能規劃 -讓訪客可以使用 Github ID 搜尋 Github 使用者,展示 Github 使用者名稱、follower、following、avatar_url 並可以返回首頁。 +## Feature planning +Visitors can use Github ID to search for Github users, display Github username, followers, following, avatar_url and return to home page. -## 使用技術 +## Tech stack 1. React 2. Redux @@ -18,19 +18,19 @@ 8. Roboto Font from Google Font 9. Github API(https://api.github.com/users/torvalds) -不過要注意的是 Github API 若沒有使用 App key 的話可以呼叫 API 的次數會受限 +Although pay note that if an App Key is not used with the Github API, API calls are limited -## 專案成果截圖 +## Project end-result screenshot ![React Redux](./images/demo-1.png "React Redux") ![React Redux](./images/demo-2.png "React Redux") -## 環境安裝與設定 -1. 安裝 Node 和 NPM +## Environment installation and configuration +1. Install Node and NPM -2. 安裝所需套件 +2. Install required packages ``` $ npm install --save react react-dom redux react-redux react-router immutable redux-immutable redux-actions whatwg-fetch redux-thunk material-ui react-tap-event-plugin @@ -40,9 +40,9 @@ $ npm install --save react react-dom redux react-redux react-router immutable re $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-1 eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react html-webpack-plugin webpack webpack-dev-server redux-logger ``` -接下來我們先設定一下開發文檔。 +Configure our development file. -1. 設定 Babel 的設定檔: `.babelrc` +1. Configure Babel settings file: `.babelrc` ```javascript { @@ -55,7 +55,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ``` -2. 設定 ESLint 的設定檔和規則: `.eslintrc` +2. Configure ESLint settings file and rules: `.eslintrc` ```javascript { @@ -69,10 +69,10 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 } ``` -3. 設定 Webpack 設定檔: `webpack.config.js` +3. Configure Webpack settings file: `webpack.config.js` ```javascript - // 讓你可以動態插入 bundle 好的 .js 檔到 .index.html + // Allows you to dynamically insert bundled .js files to .index.html const HtmlWebpackPlugin = require('html-webpack-plugin'); const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ @@ -81,7 +81,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 inject: 'body', }); - // entry 為進入點,output 為進行完 eslint、babel loader 轉譯後的檔案位置 + // entry is our entrypoint, output is the file location after processing is completed by eslint, babel loader module.exports = { entry: [ './src/index.js', @@ -108,7 +108,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 }, }], }, - // 啟動開發測試用 server 設定(不能用在 production) + // Settings for launching test (cannot be used in production) devServer: { inline: true, port: 8008, @@ -117,9 +117,9 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 }; ``` -太好了!這樣我們就完成了開發環境的設定可以開始動手實作 `Github Finder` 應用程式了! +Awesome! This concludes our development environment settings and allows us to set about writing our `Github Finder` appication! -## 動手實作 +## Putting to Practice 1. Setup Mockup @@ -139,7 +139,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ``` - 設定 `webpack.config.js` 的進入點 `src/index.js`: + Configuring `webpack.config.js` entrypoint `src/index.js`: ```javascript import React from 'react'; @@ -153,14 +153,14 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 import ResultPageContainer from './containers/ResultPageContainer'; import store from './store'; - // 引入 react-tap-event-plugin 避免 material-ui onTouchTap event 會遇到的問題 + // Import react-tap-event-plugin to prevent issues during onTouchTap event with material-ui // Needed for onTouchTap // http://stackoverflow.com/a/34015469/988941 injectTapEventPlugin(); - // 用 react-redux 的 Provider 包起來將 store 傳遞下去,讓每個 components 都可以存取到 state - // 這邊使用 browserHistory 當做 history,並使用 material-ui 的 MuiThemeProvider 包裹整個 components - // 由於這邊是簡易的 App 我們設計了 Main 為母模版,其有兩個子元件 HomePageContainer 和 ResultPageContainer,其中 HomePageContainer 為根位置的子元件 + // Use react-redux Provider to wrap store and it pass along, allowing every component to get state + // Here we use browserHistory for history, and use material-ui MuiThemeProvider to wrap all of our components + // Because this is a simple App we designed Main as a parent template, additionally there are two child components HomePageContainer and ResultPageContainer, of which HomePageContainer is the root position child component ReactDOM.render( @@ -178,7 +178,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 2. Actions - 首先先定義 actions 常數: + First we define actions constants: ```javascript export const SHOW_SPINNER = 'SHOW_SPINNER'; @@ -189,12 +189,12 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 export const CHAGE_USER_ID = 'CHAGE_USER_ID'; ``` - 現在我們來規劃我們的 actions 的部份,這個範例我們使用到了 `redux-thunk` 來處理非同步的 action(若讀者對於新的 Ajax 處理方式 fetch() 不熟悉可以先[參考這個文件](https://developer.mozilla.org/zh-TW/docs/Web/API/GlobalFetch/fetch))。以下是 `src/actions/githubActions.js` 完整程式碼: + Now we will plan out our actions, in this example we used `redux-thunk` to handle asynchronous actions (if you are unfamiliar with new Ajax handling method fetch() you can first [refer to this document](https://developer.mozilla.org/zh-TW/docs/Web/API/GlobalFetch/fetch). Below is the complete code for `src/actions/githubActions.js`: ```javascript - // 這邊引入了 fetch 的 polyfill,考以讓舊的瀏覽器也可以使用 fetch + // Here fetch polyfill is imported, considerate of allowing old browsers to use fetch import 'whatwg-fetch'; - // 引入 actionTypes 常數 + // import actionTypes constants import { GET_GITHUB_INITIATE, GET_GITHUB_SUCCESS, @@ -202,15 +202,15 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 CHAGE_USER_ID, } from '../constants/actionTypes'; - // 引入 uiActions 的 action + // import uiActions actions import { showSpinner, hideSpinner, } from './uiActions'; - // 這邊是這個範例的重點,要學習我們之前尚未講解的非同步 action 處理方式:不同於一般同步 action 直接發送 action,非同步 action 會回傳一個帶有 dispatch 參數的 function,裡面使用了 Ajax(這裡使用 fetch())進行處理 - // 一般和 API 互動的流程:INIT(開始請求/秀出 spinner)-> COMPLETE(完成請求/隱藏 spinner)-> ERROR(請求失敗) - // 這次我們雖然沒有使用 redux-actions 但我們還是維持標準 Flux Standard Action 格式:{ type: '', payload: {} } + // This part is the focus of this example, to learn about what we have not covered before - asynchronous action handling: unlike synchronous actions directly dispatching actions, asynchronous actions will return a function which has a dispatch parameter, within this we use Ajax (here we use fetch()) to process + // Usual API interaction process: INIT (begin request/ show spinner) -> COMPLETE (completed request/ hide spinner) -> Error (request failed) + // This time although we did not use redux-actions we maintained Flux Standard Action format: { type: '', payload: {} } export const getGithub = (userId = 'torvalds') => { return (dispatch) => { @@ -226,11 +226,11 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 } } - // 同步 actions 處理,回傳 action 物件 + // Synchronous actions handling, returning action object export const changeUserId = (text) => ({ type: CHAGE_USER_ID, payload: { userId: text } }); ``` - 以下是 `src/actions/uiActions.js` 負責處理 UI 的行為: + Below is `src/actions/uiActions.js` which handles UI behaviours: ```javascript import { createAction } from 'redux-actions'; @@ -239,12 +239,12 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 HIDE_SPINNER, } from '../constants/actionTypes'; - // 同步 actions 處理,回傳 action 物件 + // Synchronous actions handling, returning action object export const showSpinner = () => ({ type: SHOW_SPINNER}); export const hideSpinner = () => ({ type: HIDE_SPINNER}); ``` - 透過於 `src/actions/index.js` 將我們 actions 輸出 + Through `src/actions/index.js` we export our actions ```javascript export * from './uiActions'; @@ -253,7 +253,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 3. Reducers - 接下來我們要來設定一下 Reducers 和 models(initialState 格式)的設計,注意我們這個範例都是使用 `ImmutableJS`。以下是 `src/constants/models.js`: + Now we will configure Reducers and models (initialState format) and design, pay attention that this example entirely uses `ImmutableJS`. Below is `src/constants/models.js`: ```javascript import Immutable from 'immutable'; @@ -262,14 +262,14 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 spinnerVisible: false, }); - // 我們使用 userId 來暫存使用者 ID,data 存放 Ajax 取回的資料 + // We use userId to temporarily save user's ID, the data is saved in the information return by Ajax export const GithubState = Immutable.fromJS({ userId: '', data: {}, }); ``` - 以下是 `src/reducers/data/githubReducers.js`: + Below is `src/reducers/data/githubReducers.js`: ```javascript import { handleActions } from 'redux-actions'; @@ -283,13 +283,13 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 } from '../../constants/actionTypes'; const githubReducers = handleActions({ - // 當使用者按送出按鈕,發出 GET_GITHUB_SUCCESS action 時將接收到的資料 merge + // When user presses submit button, upon dispatching GET_GITHUB_SUCCESS action merge the received data GET_GITHUB_SUCCESS: (state, { payload }) => ( state.merge({ data: payload.data, }) ), - // 當使用者輸入使用者 ID 會發出 CHAGE_USER_ID action 時將接收到的資料 merge + // When user enters an User ID, upon dispatching CHAGE_USER_ID action merge the received data CHAGE_USER_ID: (state, { payload }) => ( state.merge({ 'userId': @@ -302,7 +302,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ``` - 以下是 `src/reducers/ui/uiReducers.js`: + Below is `src/reducers/ui/uiReducers.js`: ```javascript import { handleActions } from 'redux-actions'; @@ -313,7 +313,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 HIDE_SPINNER, } from '../../constants/actionTypes'; - // 隨著 fetch 結果顯示 spinner + // Show spinner along with fetch const uiReducers = handleActions({ SHOW_SPINNER: (state) => ( state.set( @@ -332,7 +332,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 export default uiReducers; ``` - 將 reduces 使用 `redux-immutable` 的 `combineReducers` 在一起。以下是 `src/reducers/index.js`: + Combine `redux-immutable` used by reduces with `combineReducers`. Below is `src/reducers/index.js`: ```javascript import { combineReducers } from 'redux-immutable'; @@ -346,8 +346,8 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 export default rootReducer; ``` - - 運用 redux 提供的 createStore API 把 `rootReducer`、`initialState`、`middlewares` 整合後創建出 store。以下是 `src/store/configureSotore.js` + + Using redux's createStore API to make a store from integration of `rootReducer`, `initialState` and `middlewares`. Below is `src/store/configureSotore.js` ```javascript import { createStore, applyMiddleware } from 'redux'; @@ -367,11 +367,11 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 4. Build Component - 終於我們進入了 View 的細節設計,首先我們先針對母模版,也就是每個頁面都會出現的 `AppBar` 做設計。以下是 `src/components/Main/Main.js`: + Finally we have come to the design for View details, first we should target the design of our parent template, which is the `AppBar` which appears on every page. Below is `src/components/Main/Main.js`: ```javascript import React from 'react'; - // 引入 AppBar + // import AppBar import AppBar from 'material-ui/AppBar'; const Main = (props) => ( @@ -386,7 +386,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ); - // 進行 propTypes 驗證 + // Commence propTypes validation Main.propTypes = { children: React.PropTypes.object, }; @@ -394,11 +394,11 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 export default Main; ``` - 以下是 `src/components/ResultPage/HomePage.js`: + Below is `src/components/ResultPage/HomePage.js`: ```javascript import React from 'react'; - // 使用 react-router 的 Link 當做超連結,傳送 userId 當作 query + // Using react-router Link for hyperlinking, send userId as query import { Link } from 'react-router'; import RaisedButton from 'material-ui/RaisedButton'; import TextField from 'material-ui/TextField'; @@ -427,7 +427,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 export default HomePage; ``` - 以下是 `src/components/ResultPage/ResultPage.js`,將 `userId` 當作 `props` 傳給 ``: + Below is `src/components/ResultPage/ResultPage.js`, using `userId` as `props` to send to ``: ```javascript @@ -443,16 +443,16 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 export default ResultPage; ``` - 以下是 `src/components/GithubBox/GithubBox.js`,負責擷取的 Github 資料呈現: + Below is `src/components/GithubBox/GithubBox.js`, handles the appearance of acquired Github information: ```javascript import React from 'react'; import { Link } from 'react-router'; - // 引入 material-ui 的卡片式元件 + // Import material-ui cardlike component import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card'; - // 引入 material-ui 的 RaisedButton + // Import material-ui RaisedButton import RaisedButton from 'material-ui/RaisedButton'; - // 引入 ActionHome icon + // Import ActionHome icon import ActionHome from 'material-ui/svg-icons/action/home'; const GithubBox = (props) => ( @@ -487,7 +487,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 5. Connect State to Component - 最後,我們要將 Container 和 Component 連接在一起(若忘記了,請先回去複習 Container 與 Presentational Components 入門!)。以下是 `src/containers/HomePage/HomePage.js`,負責將 userId 和使用到的事件處理方法用 props 傳進 component : + Lastly, we want to link Container with Component (if you have forgotten this, first go back to practice Container and Presentational Components INtroduction!). Below is `src/containers/HomePage/HomePage.js`, which is responsible for using props to send userId and the used event handling methods to component: ```javascript import { connect } from 'react-redux'; @@ -520,7 +520,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 )(HomePage); ``` - 以下是 `src/containers/ResultPage/ResultPage.js`: + Below is `src/containers/ResultPage/ResultPage.js`: ```javascript import { connect } from 'react-redux'; @@ -536,26 +536,26 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 6. That's it - 若一切順利的話,這時候你可以在終端機下 `$ npm start` 指令,然後在 `http://localhost:8008` 就可以看到你的努力成果囉! + If everything was successful, at this time within the terminal you can use `$ npm start` command, and at `http://localhost:8008` you can see the fruits of your effort! ![React Redux](./images/demo-1.png "React Redux") -## 總結 -本章帶領讀者們從零開始整合 React + Redux + ImmutableJS + React Router 搭配 Github API 製作一個簡單的 Github 使用者查詢應用。下一章我們將挑戰進階應用,學習 Server Side Rendering 方面的知識,並用 React + Redux + Node(Isomorphic)開發一個食譜分享網站。 +## Summary +This chapter guided readers to integrate from scratch React + Redux + ImmutableJS + React Router paired with Github API to create a simple Github user search appication. In the next article we will tackle advanced apps, learn Server Side Rendering related knowledge, and use React + Redux + Node(Isomorphic) to develop a receipe sharing website. -## 延伸閱讀 +## Extended Reading 1. [Tutorial: build a weather app with React](http://joanmira.com/tutorial-build-a-weather-app-with-react/) 2. [OpenWeatherMap](http://openweathermap.org/) 3. [Weather Icons](https://erikflowers.github.io/weather-icons/) 4. [Weather API Icons](https://erikflowers.github.io/weather-icons/api-list.html) 5. [Material UI](http://www.material-ui.com/#/) -6. [【翻译】这个API很“迷人”——(新的Fetch API)](http://www.w3ctech.com/topic/854) +6. [This API is so Fetching!](https://hacks.mozilla.org/2015/03/this-api-is-so-fetching/) 7. [Redux: trigger async data fetch on React view event](http://stackoverflow.com/questions/33304225/redux-trigger-async-data-fetch-on-react-view-event) 8. [Github API](https://api.github.com/) -9. [传统 Ajax 已死,Fetch 永生](https://github.com/camsong/blog/issues/2) +9. [Traditional Ajax is dead, long live Fetch](https://github.com/camsong/blog/issues/2) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:Container 與 Presentational Components 入門](https://github.com/kdchang/reactjs101/blob/master/Ch08/container-presentational-component-.md) | [下一章:React Redux Sever Rendering(Isomorphic JavaScript)入門](https://github.com/kdchang/reactjs101/blob/master/Ch10/react-redux-server-rendering-isomorphic-javascript.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Container and Presentational Components introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch08/container-presentational-component-.md) | [Next article: React Redux Server Rendering (Isomorphic JavaScript) Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch10/react-redux-server-rendering-isomorphic-javascript.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch10/README.md b/Ch10/README.md index 8ca313b..a2bdf3e 100644 --- a/Ch10/README.md +++ b/Ch10/README.md @@ -1,7 +1,7 @@ -# Ch10 實戰教學:用 React + Redux + Node(Isomorphic JavaScript)開發食譜分享網站 +# Learn by Writing: Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website -1. [React Redux Sever Rendering(Isomorphic JavaScript)入門](https://github.com/kdchang/reactjs101/blob/master/Ch10/react-redux-server-rendering-isomorphic-javascript.md) -2. [用 React + Redux + Node(Isomorphic JavaScript)開發一個食譜分享網站](https://github.com/kdchang/reactjs101/blob/master/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md) +1. [React Redux Server Rendering (Isomorphic JavaScript) Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch10/react-redux-server-rendering-isomorphic-javascript.md) +2. [Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](https://github.com/sycherng/reactjs101/blob/en-US/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | \ No newline at end of file +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | diff --git a/Ch10/react-redux-server-rendering-isomorphic-javascript.md b/Ch10/react-redux-server-rendering-isomorphic-javascript.md index 5c6771b..250f4c6 100644 --- a/Ch10/react-redux-server-rendering-isomorphic-javascript.md +++ b/Ch10/react-redux-server-rendering-isomorphic-javascript.md @@ -1,38 +1,38 @@ -# React Redux Sever Rendering(Isomorphic JavaScript)入門 +# React Redux Server Rendering (Isomorphic JavaScript) Introduction -![React Redux Sever Rendering(Isomorphic)入門](./images/isomorphic-javascript.png "React Redux Sever Rendering(Isomorphic)入門") +![React Redux Server Rendering (Isomorphic JavaScript) Introduction](./images/isomorphic-javascript.png "React Redux Server Rendering (Isomorphic JavaScript) Introduction") -## 前言 -由於可能有些讀者沒聽過 [Isomorphic JavaScript](http://isomorphic.net/) 。因此在進到開發 React Redux Sever Rendering 應用程式的主題之前我們先來聊聊 Isomorphic JavaScript 這個議題。 +## Foreword +Because some readers may not have heard of [Isomorphic JavaScript](http://isomorphic.net/). Therefore before we begin developing a React Redux Server-side Rendering application we will first chat about the topic of Isomorphic JavaScript. -根據 [Isomorphic JavaScript](http://isomorphic.net/) 這個網站的說明: +According to [Isomorphic JavaScript](http://isomorphic.net/) website's explanation: >Isomorphic JavaScript Isomorphic JavaScript apps are JavaScript applications that can run both client-side and server-side. The backend and frontend share the same code. -Isomorphic JavaScript 係指瀏覽器端和伺服器端共用 JavaScript 的程式碼。 +Isomorphic JavaScript refers to the JavaScript code that is shared between browser-side and server-side. -另外,除了 Isomorphic JavaScript 外,讀者或許也有聽過 Universal JavaScript 這個用詞。那什麼是 Universal JavaScript 呢?它和 Isomorphic JavaScript 是指一樣的意思嗎?針對這個議題網路上有些開發者提出了自己的觀點: [Universal JavaScript](https://medium.com/@mjackson/universal-javascript-4761051b7ae9#.67xsay73m)、[Isomorphism vs Universal JavaScript](https://medium.com/@ghengeveld/isomorphism-vs-universal-javascript-4b47fb481beb#.qvggcp3v8)。其中 Isomorphism vs Universal JavaScript 這篇文章的作者 Gert Hengeveld 指出 `Isomorphic JavaScript` 主要是指前後端共用 JavaScript 的開發方式,而 `Universal JavaScript` 是指 JavaScript 程式碼可以在不同環境下運行,這當然包含瀏覽器端和伺服器端,甚至其他環境。也就是說 `Universal JavaScript` 在意義上可以涵蓋的比 `Isomorphic JavaScript` 更廣泛一些,然而在 Github 或是許多技術討論上通常會把兩者視為同一件事情,這部份也請讀者留意。 +Additionally, in addition to Isomorphic JavaScript, readers may have also heard of the term Universal JavaScript. What is Universal JavaScript? Is it the same as Isomorphic JavaScript? Regarding this topic the developers on the web raised their own viewpoint: [Universal JavaScript](https://medium.com/@mjackson/universal-javascript-4761051b7ae9#.67xsay73m), [Isomorphism vs Universal JavaScript](https://medium.com/@ghengeveld/isomorphism-vs-universal-javascript-4b47fb481beb#.qvggcp3v8). In particular Isomorphism vs Universal JavaScript author Gert Hengeveld points out `Isomorphic JavaScript` mainly points to the development strategy of sharing JavaScript between front- and back-end, while `Universal JavaScript` refers to JavaScript code that can be executed in different environments, of course this includes browser-end and server-side, even including other environments. In other words `Universal JavaScript`'s meaning covers slightly more than `Isomorphic JavaScript`, However on Github and many technology forum discussions the two are viewed as the same, please pay note to this. -## Isomorphic JavaScript 的好處 -在開始真正撰寫 Isomorphic JavaScript 前我們在進一步探討使用 Isomorphic JavaScript 有哪些好處?在談好處之前,我們先看看最早 Web 開發是如何處理頁面渲染和 state 管理,還有遇到哪些挑戰。 +## Isomorphic JavaScript benefits +Before we start writing Isomorphic JavaScript we should take a deeper look at what the benefits of using Isomorphic JavaScript are. Before discussing the benefits, we will first take a look at how page rendering and state management was accomplished during early web development, and what challenges were encountered. -最早的時候我們談論 Web 很單純,都是由 Server 端進行模版的處理,你可以想成 template 是一個函數,我們傳送資料進去,template 最後產生一張 HTML 給瀏覽器顯示。例如:Node 使用的([EJS](http://ejs.co/)、[Jade](http://jade-lang.com/))、Python/Django 的 [Template](https://docs.djangoproject.com/el/1.10/ref/templates/) 或替代方案 [Jinja](https://github.com/pallets/jinja)、PHP 的 [Smarty](http://www.smarty.net/)、[Laravel](https://laravel.com/) 使用的 [Blade](https://laravel.com/docs/5.0/templates),甚至是 Ruby on Rails 用的 [ERB](http://guides.rubyonrails.org/layouts_and_rendering.html)。都是由後端去 render 所有資料和頁面,前端處理相對單純。 +In the earliest days our conversation about the Web was very simple, everything was handled with server side templates, you can think of templates as a function, we would send data to it, the template would generate an HTML file for the browser to display. For example: ([EJS](http://ejs.co/) and [Jade](http://jade-lang.com/)) for Node, [Template](https://docs.djangoproject.com/el/1.10/ref/templates/) or substitute [Jinja](https://github.com/pallets/jinja) for Python/Django, [Smarty](http://www.smarty.net/) for PHP, [Blade](https://laravel.com/docs/5.0/templates) for [Laravel](https://laravel.com/), even [ERB](http://guides.rubyonrails.org/layouts_and_rendering.html) for Ruby on Rails. It all used the server-side to render all data and the page, the front-end processing was relatively simple. -然而隨著前端工程的軟體工程化和使用者體驗的要求,開始出現各式前端框架的百花齊放,例如:[Backbone.js](http://backbonejs.org/)、[Ember.js](http://emberjs.com/) 和 [Angular.js](https://angularjs.org/) 等前端 MVC (Model-View-Controller) 或 MVVM (Model-View-ViewModel) 框架,將頁面於前端渲染的不刷頁單頁式應用程式(Single Page App)也因此開始流行。 +However as front-end development veered towards using more software engineering and demanding user experience, all kinds of front-end frameworks appeared like a hundreds of flowers blooming simultaneously, for example: [Backbone.js](http://backbonejs.org/), [Ember.js](http://emberjs.com/) and [Angular.js](https://angularjs.org/) etc front-end MVC (Model-View-Controller) or MVVM (Model-View-ViewModel) frameworks, Single Page Apps which use front-end rendering of the UI thus began to gain popularity. -後端除了提供初始的 HTML 外,還提供 API Server 讓前端框架可以取得資料用於前端 template。複雜的邏輯由 ViewModel/Presenter 來處理,前端 template 只處理簡單的是否顯示或是元素迭代的狀況,如下圖所示: +Aside from providing initial HTML, the back-end also provided an API Server for the front-end framework to obtain data for use in the front-end template. Complex logic was handled by ViewModel/Presenter, front-end template only processes simple scenarios like whether to display items as well as element iteration, shown in the image below: -![React Redux Sever Rendering(Isomorphic)入門](./images/client-mvc.png "React Redux Sever Rendering(Isomorphic)入門") +![React Redux Server Rendering (Isomorphic JavaScript) Introduction](./images/client-mvc.png "React Redux Server Rendering (Isomorphic JavaScript) Introduction") -然而前端渲染 template 雖然有它的好處但也遇到一些問題包括效能、SEO 等議題。此時我們就開始思考 Isomorphic JavaScript 的可能性:為什麼我們不能前後端都使用 JavaScript 甚至是 React? +However although front-end rendering templates had their benefits, it also met with several problems including performance, SEO etc topics. At this time we began musing on the potential of Isomorphic JavaScript: why can't we use JavaScript or even React on both the front- and back-end? -![React Redux Sever Rendering(Isomorphic)入門](./images/isomorphic-api.png "React Redux Sever Rendering(Isomorphic)入門") +![React Redux Server Rendering (Isomorphic JavaScript) Introduction](./images/isomorphic-api.png "React Redux Server Rendering (Isomorphic JavaScript) Introduction") -事實上,React 的優勢就在於它可以很優雅地實現 Server Side Rendering 達到 Isomorphic JavaScript 的效果。在 `react-dom/server` 中有兩個方法 `renderToString` 和 `renderToStaticMarkup` 可以在 server 端渲染你的 components。其主要都是將 React Component 在 Server 端轉成 DOM String,也可以將 props 往下傳,然而事件處理會失效,要到 client-side 的 React 接收到後才會把它加上去(但要注意 server-side 和 client-side 的 checksum 要一致不然會出現錯誤),這樣一來可以提高渲染速度和 SEO 效果。`renderToString` 和 `renderToStaticMarkup` 最大的差異在於 `renderToStaticMarkup` 會少加一些 React 內部使用的 DOM 屬性,例如:`data-react-id`,因此可以節省一些資源。 +Actually, React's advantage lies in how it can very elegantly realize Server Side Rendering to achieve the effect of Isomorphic JavaScript. In `react-dom/server` there are two methods `renderToString` and `renderToStaticMarkup` which allows server-side rendering of your components. In mainly is used for Server-side convertion of a React Component to a DOM String, it can also be used to propagate props, however event handling will fail, requiring the client-side React to receive it before adding on (but pay note that the server-side and client-side checksum had to match or else errors would appear), this allows increase of rendering speed and SEO effects. `renderToString` and `renderToStaticMarkup`'s biggest difference is in `renderToStaticMarkup` adding less DOM props used internally by React, for example: `data-react-id`, thereby saving some resources. -使用 `renderToString` 進行 Server 端渲染: +Using `renderToString` for Server-side rendering: ```javascript import ReactDOMServer from 'react-dom/server'; @@ -40,7 +40,7 @@ import ReactDOMServer from 'react-dom/server'; ReactDOMServer.renderToString(); ``` -渲染出來的效果: +Effect after rendering: ```html ``` -總的來說使用 Isomorphic JavaScript 會有以下的好處: +All said using Isomorphic JavaScript provides the below benefits: -1. 有助於 SEO -2. Rendering 速度較快,效能較佳 -3. 放棄蹩腳的 Template 語法擁抱 Component 元件化思考,便於維護 -4. 盡量前後端共用程式碼節省開發時間 +1. Assists in SEO +2. Rendering speed is faster, performance is better +3. Discarding shoddy Template syntax and embracing Component oriented thinking, for convenience in maintenance +4. Sharing code between front- and back-end as much as possible to reduce development time -不過要注意的是如果有使用 Redux 在 Server Side Rendering 中,其流程相對複雜,不過大致流程如下: -由後端預先載入需要的 initialState,由於 Server 渲染必須全部都轉成 string,所以先將 state 先 dehydration(脫水),等到 client 端再 rehydration(覆水),重建 store 往下傳到前端的 React Component。 +However carefully note that if using Redux with Server Side Rendering, the workflow is relatively complex, in general the workflow is as follows: +Using back-end load the initialState, because Server rendering must all be converted to string, first we "dehydrate" our state, wait until it reaches the client-side before "rehydrating", rebuilding a store to pass on to front-end React Components. -而要把資料從伺服器端傳遞到客戶端,我們需要: +To transfer information from server-side to client-side, we need to: -1. 把取得初始 state 當做參數並對每個請求建立一個全新的 Redux store 實體 -2. 選擇性地 dispatch 一些 action -3. 把 state 從 store 取出來 -4. 把 state 一起傳到客戶端 +1. Use the acquired initial state as a parameter to establish an all-new Redux store for every request +2. Selectively dispatch some actions +3. Get state from store +4. Send the state together to client-side -接下來我們就開始動手實作一個簡單的 React Server Side Rendering Counter 應用程式。 +Below we will put this to practice by writing a simple React Server Side Rendering Counter application. -## 專案成果截圖 +## Project result screenshot -![React Redux Sever Rendering(Isomorphic)入門](./images/react-server-rendering-demo.png "React Redux Sever Rendering(Isomorphic)入門") +![React Redux Server Rendering (Isomorphic JavaScript) Introduction](./images/react-server-rendering-demo.png "React Redux Server Rendering (Isomorphic JavaScript) Introduction") -## 環境安裝與設定 -1. 安裝 Node 和 NPM +## Environment installation and configuration +1. Install Node and NPM -2. 安裝所需套件 +2. Install needed packages ``` $ npm install --save react react-dom redux react-redux react-router immutable redux-immutable redux-actions redux-thunk babel-polyfill babel-register body-parser express morgan qs @@ -84,9 +84,9 @@ ReactDOMServer.renderToString(); $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-1 eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react html-webpack-plugin webpack webpack-dev-server redux-logger ``` -接下來我們先設定一下開發文檔。 +Next we first configure a development file. -1. 設定 Babel 的設定檔: `.babelrc` +1. Configure Babel settings file: `.babelrc` ```javascript { @@ -98,7 +98,7 @@ ReactDOMServer.renderToString(); } ``` -2. 設定 ESLint 的設定檔和規則: `.eslintrc` +2. Configure ESLint settings file and rules: `.eslintrc` ```javascript { @@ -112,10 +112,10 @@ ReactDOMServer.renderToString(); } ``` -3. 設定 Webpack 設定檔: `webpack.config.js` +3. Configure Webpack settings file: `webpack.config.js` ```javascript - // 讓你可以動態插入 bundle 好的 .js 檔到 .index.html + // Allowing you to dynamically insert bundled .js files to .index.html const HtmlWebpackPlugin = require('html-webpack-plugin'); const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({ @@ -124,7 +124,7 @@ ReactDOMServer.renderToString(); inject: 'body', }); - // entry 為進入點,output 為進行完 eslint、babel loader 轉譯後的檔案位置 + // entry is the entrypoint, output is the file location after processing by eslint and babel loader module.exports = { entry: [ './src/index.js', @@ -151,7 +151,7 @@ ReactDOMServer.renderToString(); }, }], }, - // 啟動開發測試用 server 設定(不能用在 production) + // Settings for dev server launch (cannot be used in production) devServer: { inline: true, port: 8008, @@ -160,18 +160,18 @@ ReactDOMServer.renderToString(); }; ``` -太好了!這樣我們就完成了開發環境的設定可以開始動手實作 `React Server Side Rendering Counter` 應用程式了! +Awsome! This concludes our development environment settings so we can get started on writing our `React Server Side Rendering Counter` app! -先看一下我們整個專案的資料結構,我們把整個專案分成三個主要的資料夾(`client`、`server`,還有共用程式碼的 `common`): +First we will look at the overall project data structure, we separate the project into three main folders (`client`, `server`, and mutually used code in `common`): -![React Redux Sever Rendering(Isomorphic)入門](./images/react-server-rendering-folder.png "React Redux Sever Rendering(Isomorphic)入門") +![React Redux Sever Rendering (Isomorphic) Introduction](./images/react-server-rendering-folder.png "React Redux Sever Rendering (Isomorphic) Introduction") -## 動手實作 +## Putting to Practice -首先,我們先定義了 `client` 的 `index.js`: +First we define `client`'s `index.js`: ```javascript -// 引用 babel-polyfill 避免瀏覽器不支援部分 ES6 用法 +// Import babel-polyfill to avoid the browser not supporting some ES6 methods import 'babel-polyfill'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -180,13 +180,13 @@ import CounterContainer from '../common/containers/CounterContainer'; import configureStore from '../common/store/configureStore' import { fromJS } from 'immutable'; -// 從 server 取得傳進來的 initialState。由於從字串轉回物件,又稱為 rehydration(覆水) +// From server get the incoming initialState. Converting a string back to object, also known as rehydration const initialState = window.__PRELOADED_STATE__; -// 由於我們使用 ImmutableJS,所以需要把在 server-side dehydration(脫水)又在前端 rehydration(覆水)的 initialState 轉成 ImmutableJS 資料型態,並傳進 configureStore 建立 store +// Because we use ImmutableJS, we need to convert the initialState which has undergone server-side dehydration and front-end rehydration to a ImmutableJS object, and send it to configureStore to build a store const store = configureStore(fromJS(initialState)); -// 接下來就跟一般的 React App 一樣,把 store 透過 Provider 往下傳到 Component 中 +// The remaining is the same as a normal React App, send store to Component via Provider ReactDOM.render( @@ -196,7 +196,7 @@ ReactDOM.render( ``` -由於 Node 端要到新版對於 ES6 支援較好,所以先用 `babel-register` 在 `src/server/index.js` 去即時轉譯 `server.js`,但目前不建議在 `production` 環境使用。 +Because Node better supports ES6 at latest versions, we first use `babel-register` in `src/server/index.js` to extemporaneously translate `server.js`, but currently I do not recommend using this in a `production` environment. ```javascript // use babel-register to precompile ES6 syntax @@ -204,7 +204,7 @@ require('babel-register'); require('./server'); ``` -接著是我們 `server` 端,也是這個範例最重要的一個部分。首先我們用 `express` 建立了一個 port 為 3000 的 server,並使用 webpack 去執行 `client` 的程式碼。這個範例中我們使用了 `handleRender` 當 request 進來時(直接拜訪頁面或重新整理)就會執行 fetchCounter() 進行處理: +Next is our `server`-side, which is also the most important part of this example. First we use `express` to set a port to 3000 and server, and use webpack to execute `client` code. In this example we used `handleRender` to call fetchCounter() when requests come in (when visiting our page or on refresh): ```javascript import Express from 'express'; @@ -229,33 +229,33 @@ const app = new Express(); const port = 3000; function handleRender(req, res) { - // 模仿實際非同步 api 處理情形 + // imitating an actual asynchronous api handling scenario fetchCounter(apiResult => { - // 讀取 api 提供的資料(這邊我們 api 是用 setTimeout 進行模仿非同步狀況),若網址參數有值擇取值,若無則使用 api 提供的隨機值,若都沒有則取 0 + // read the information provided by the api (here our api uses setTimeout to imitate an asynchronous situation), if the web address parameter has a value get the value, if not use api to provide a random value, if neither then get 0 const params = qs.parse(req.query); const counter = parseInt(params.counter, 10) || apiResult || 0; - // 將 initialState 轉成 immutable 和符合 state 設計的格式 + // convert initialState to a state format compatible with immutablejs const initialState = fromJS({ counterReducers: { count: counter, } }); - // 建立一個 redux store + // make a redux store const store = configureStore(initialState); - // 使用 renderToString 將 component 轉為 string + // use renderToString to convert component to string const html = renderToString( ); - // 從建立的 redux store 中取得 initialState + // get the initialState from the redux store that was made const finalState = store.getState(); - // 將 HTML 和 initialState 傳到 client-side + // send HTML and initialState to client-side res.send(renderFullPage(html, finalState)); }) } -// HTML Markup,同時也把 preloadedState 轉成字串(stringify)傳到 client-side,又稱為 dehydration(脫水) +// HTML Markup, at the same time stringify preloadedState and send to client-side, aka dehydration function renderFullPage(html, preloadedState) { return ` @@ -274,14 +274,14 @@ function renderFullPage(html, preloadedState) { ` } -// 使用 middleware 於 webpack 去進行 hot module reloading +// use middleware and webpack to commence hot module reloading const compiler = webpack(webpackConfig); app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath })); app.use(webpackHotMiddleware(compiler)); -// 每次 server 接到 request 都會呼叫 handleRender +// every time the server receives request call handleRender app.use(handleRender); -// 監聽 server 狀況 +// listen to server situation app.listen(port, (error) => { if (error) { console.error(error) @@ -291,7 +291,7 @@ app.listen(port, (error) => { }); ``` -處理完 Server 的部份接下來我們來處理 actions 的部份,在這個範例中 actions 相對簡單,主要就是新增和減少兩個行為,以下為 `src/actions/counterActions.js`: +After handling our Server we will now handle our actions, the actions in this example are relatively simple, mainly behaviours to increase or decrease, below is `src/actions/counterActions.js`: ```javascript import { createAction } from 'redux-actions'; @@ -304,14 +304,14 @@ export const incrementCount = createAction(INCREMENT_COUNT); export const decrementCount = createAction(DECREMENT_COUNT); ``` -以下為輸出常數 `src/constants/actionTypes.js`: +Below is `src/constants/actionTypes.js` which exports constants: ```javascript export const INCREMENT_COUNT = 'INCREMENT_COUNT'; export const DECREMENT_COUNT = 'DECREMENT_COUNT'; ``` -在這個範例中我們使用 `setTimeout()` 來模擬非同步的產生資料讓 server 端在每次接收 request 時讀取隨機產生的值。實務上,我們會開 API 讓 Server 讀取初始要匯入的 initialState。 +In this example we used `setTimeout()` to imitate an asynchrously generated information allowing server to read a randomly generated value every time a request is received. In practice, we would open our API to allow Server to get the initialSTate. ```javascript function getRandomInt(min, max) { @@ -325,7 +325,7 @@ export function fetchCounter(callback) { } ``` -談完 actions 我們來看我們的 reducers,在這個範例中 reducers 也是相對簡單的,主要就是針對新增和減少兩個行為去 set 值,以下是 `src/reducers/counterReducers.js`: +After discussing actions we should look at our reducers, the reducers in this example is also relatively simple, mainly they target the increase and decrease behaviours to set a value, below is `src/reducers/counterReducers.js`: ```javascript import { fromJS } from 'immutable'; @@ -355,7 +355,7 @@ const counterReducers = handleActions({ export default counterReducers; ``` -準備好了 `rootReducer` 就可以使用 `createStore` 來創建我們 store,值得注意的是由於 `configureStore` 需要被 client-side 和 server-side 使用,所以把它輸出成 function 方便傳入 initialState 使用。以下是 `src/store/configureStore.js`: +Onec we have prepared `rootReducer` we can use `createStore` to create our store, worth noting that because `configureStore` needs to be used by both client-side and server-side, we export it as a function so it is convenient to import to initialState. Below is `src/store/configureStore.js`: ```javascript import { createStore, applyMiddleware } from 'redux'; @@ -373,7 +373,7 @@ export default function configureStore(preloadedState) { } ``` -最後來到了 `components` 和 `containers` 的時間,這次我們的 Component 主要有兩個按鈕讓使用者可以新增和減少數字並顯示目前數字。以下是 `src/components/Counter/Counter.js`: +Finally it is time for `components` and `containers`, this time our Component chiefly has two buttons allowing users to increase or decrease numbers and display the current number. Below is `src/components/Counter/Counter.js`: ```javascript import React, { Component, PropTypes } from 'react' @@ -397,7 +397,7 @@ const Counter = ({

    ); -// 注意要檢查 propTypes 和給定預設值 +// Pay note to check propTypes and provide default values Counter.propTypes = { count: PropTypes.number.isRequired, onIncrement: PropTypes.func.isRequired, @@ -413,7 +413,7 @@ Counter.defaultProps = { export default Counter; ``` -最後把取出的 `count ` 和事件處理方法用 connect 傳到 `Counter` 就大功告成了!以下是 `src/containers/CounterContainer/CounterContainer.js`: +Finally use connect to send the extracted `count` and event handler to `Counter` and our work is done! Below is `src/containers/CounterContainer/CounterContainer.js`: ```javascript import 'babel-polyfill'; @@ -440,14 +440,14 @@ export default connect( )(Counter); ``` -若一切順利,在終端機打上 `$ npm start`,你將可以在瀏覽器的 `http://localhost:3000` 看到自己的成果! +If everything was successful, enter `$ npm start` in the terminal, and at `http://localhost:3000` you can see your results! -![React Redux Sever Rendering(Isomorphic)入門](./images/react-server-rendering-demo.png "React Redux Sever Rendering(Isomorphic)入門") +![React Redux Sever Rendering (Isomorphic) Introduction](./images/react-server-rendering-demo.png "React Redux Sever Rendering (Isomorphic) Introduction") -## 總結 -本章闡述了 Web 頁面瀏覽的進程和 Isomorphic JavaScript 的優勢,並介紹了如何使用 React Redux 進行 Server Side Rendering 的應用程式設計。下一個章節我們將整合後端資料庫,運用 React + Redux + Node(Isomorphic)開發一個簡單的食譜分享網站。 +## Summary +This article expounded on the advancements of Webpage browsing and the advantages of Isomorphic JavaScript, as well as introduced how to use React Redux to make an app that uses Server Side Rendering. In the next article we integrate a back-end database, using React + Redux + Node (Isomorphic) to develop a simple recipe-sharing website. -## 延伸閱讀 +## Extended Reading 1. [DavidWells/isomorphic-react-example](https://github.com/DavidWells/isomorphic-react-example) 2. [RickWong/react-isomorphic-starterkit](https://github.com/RickWong/react-isomorphic-starterkit) 3. [Server-rendered React components in Rails](https://www.bensmithett.com/server-rendered-react-components-in-rails/) @@ -457,9 +457,9 @@ export default connect( 7. [Isomorphic JavaScript: The Future of Web Apps](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/) 8. [React Router Server Rendering](https://github.com/reactjs/react-router-tutorial/tree/master/lessons/13-server-rendering) -(image via [airbnb](http://nerds.airbnb.com/wp-content/uploads/2013/11/Screen-Shot-2013-11-06-at-5.21.00-PM.png)) +(image via [airbnb](http://nerds.airbnb.com/wp-content/uploads/2013/11/Screen-Shot-2013-11-06-at-5.21.00-PM.png)) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:用 React + Router + Redux + ImmutableJS 寫一個 Github 查詢應用](https://github.com/kdchang/reactjs101/blob/master/Ch09/react-router-redux-github-finder.md) | [下一章:用 React + Redux + Node(Isomorphic JavaScript)開發食譜分享網站](https://github.com/kdchang/reactjs101/blob/master/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: Using React + Router + Redux + ImmutableJS to write a Github search app](https://github.com/sycherng/reactjs101/blob/en-US/Ch09/react-router-redux-github-finder.md) | [Next article: Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](https://github.com/sycherng/reactjs101/blob/en-US/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md b/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md index 09f6a49..57db731 100644 --- a/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md +++ b/Ch10/react-router-redux-node-isomorphic-javascript-open-cook.md @@ -1,18 +1,19 @@ -# 用 React + Redux + Node(Isomorphic JavaScript)開發食譜分享網站 +# Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website -## 前言 -如果你是從一開始跟著我們踏出 React 旅程的讀者真的恭喜你,也謝謝你一路跟著我們的學習腳步,對一個初學者來說這一段路並不容易。本章是扣除附錄外我們最後一個正式章節的範例,也是規模最大的一個,在這個章節中我們要整合過去所學和添加一些知識開發一個可以登入會員並分享食譜的社群網站,Les's GO! +## Foreword +If you are a reader that has accompanied us on this React journey from ths start congratulations, and thank you for following our steps of learning, for a beginner this road is not easy. This article is the final formal article with example aside from the appendix, it is also the largest in scale, in this article we will integrate everything we have learned and add some knowledge to develop + a recipe sharing community website with member login, let's GO! -## 需求規劃 -讓使用者可以登入會員並分享食譜的社群網站 +## Requirement planning +A community website that allows members to log in and share recipes. -## 功能規劃 +## Feature planning 1. React Router / Redux / Immutable / Server Render / Async API -2. 使用者登入/登出(JSON Web Token) -3. CRUD 表單資料處理 -4. 資料庫串接(ORM/MongoDB) +2. User login/logout (JSON Web Token) +3. CRUD form handling +4. Connection to database (ORM/MongoDB) -## 使用技術 +## Technologies used 1. React 2. Redux(redux-actions/redux-promise/redux-immutable) 3. React Router @@ -24,20 +25,20 @@ 9. Webpack 10. UUID -## 專案成果截圖 +## Project result screenshots -![用 React + Redux + Node(Isomorphic)開發一個食譜分享網站](./images/open-cook-demo-1.png "用 React + Redux + Node(Isomorphic)開發一個食譜分享網站") +![Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](./images/open-cook-demo-1.png "Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website") -![用 React + Redux + Node(Isomorphic)開發一個食譜分享網站](./images/open-cook-demo-2.png "用 React + Redux + Node(Isomorphic)開發一個食譜分享網站") +![Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](./images/open-cook-demo-2.png "Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website") -![用 React + Redux + Node(Isomorphic)開發一個食譜分享網站](./images/open-cook-demo-3.png "用 React + Redux + Node(Isomorphic)開發一個食譜分享網站") +![Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](./images/open-cook-demo-3.png "Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website") -![用 React + Redux + Node(Isomorphic)開發一個食譜分享網站](./images/open-cook-demo-4.png "用 React + Redux + Node(Isomorphic)開發一個食譜分享網站") +![Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](./images/open-cook-demo-4.png "Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website") -## 環境安裝與設定 -1. 安裝 Node 和 NPM +## Environment installation and configuration +1. Install Node and NPM -2. 安裝所需套件 +2. Install needed packages ``` $ npm install --save react react-dom redux react-redux react-router immutable redux-immutable redux-actions redux-promise bcrypt body-parser cookie-parser debug express immutable jsonwebtoken mongoose morgan passport passport-local react-router-bootstrap axios serve-favicon validator uuid @@ -47,9 +48,9 @@ $ npm install --save react react-dom redux react-redux react-router immutable re $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-1 eslint eslint-config-airbnb eslint-loader eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react html-webpack-plugin webpack webpack-dev-server redux-logger ``` -接下來我們先設定一下開發文檔。 +Next we will configure our development files. -1. 設定 Babel 的設定檔: `.babelrc` +1. Configure Babel settings file: `.babelrc` ```javascript { @@ -62,7 +63,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 ``` -2. 設定 ESLint 的設定檔和規則: `.eslintrc` +2. Configure ESLint settings file and rules: `.eslintrc` ```javascript { @@ -76,7 +77,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 } ``` -3. 設定 Webpack 設定檔: `webpack.config.js`: +3. Configure Webpack settings file: `webpack.config.js`: ```javascript import webpack from 'webpack'; @@ -99,7 +100,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 exclude: /bundle\.js$/, }, ], - // 使用 Hot Module Replacement 外掛 + // Use Hot Module Replacement plugins plugins: [ new webpack.optimize.OccurrenceOrderPlugin(), new webpack.HotModuleReplacementPlugin() @@ -116,7 +117,7 @@ $ npm install --save-dev babel-core babel-eslint babel-loader babel-preset-es201 }; ``` -4. 設定 `src/server/config/index.js`: +4. Configure `src/server/config/index.js`: ```javascript export default ({ @@ -125,15 +126,15 @@ export default ({ }); ``` -太好了!這樣我們就完成了開發環境的設定可以開始動手實作我們的食譜分享社群應用程式了! +Awesome! This concludes our environment settings and allows us to get started on our community recipe sharing app! -同時我們也初步設計我們資料夾結構,主要我們將資料夾分為 `client`、`common`、`server`: +At the same time we will begin designing our folder structure, we will mainly divide the directories as `client`, `common`, `server`: -![用 React + Redux + Node(Isomorphic)開發一個食譜分享網站](./images/open-cook-demo-folder.png "用 React + Redux + Node(Isomorphic)開發一個食譜分享網站") +![Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](./images/open-cook-demo-folder.png "Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website") -## 動手實作 +## Putting to practice -首先我們先進行 `src/client/index.js` 的設計: +First we will design `src/client/index.js`: ```javascript import React from 'react'; @@ -141,15 +142,15 @@ import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import { browserHistory, Router } from 'react-router'; import { fromJS } from 'immutable'; -// 我們的 routing 放置在 common 資料夾中的 routes +// We placed routing in routes under common directory import routes from '../common/routes'; import configureStore from '../common/store/configureStore'; import { checkAuth } from '../common/actions'; -// 將 server side 傳過來的 initialState 給 rehydration(覆水) +// Rehydrate the initialState sent from server side const initialState = window.__PRELOADED_STATE__; -// 將 initialState 傳給 configureStore 函數創建出 store 並傳給 Provider +// Send initialState to configureStore function to build a store and send to Provider const store = configureStore(fromJS(initialState)); ReactDOM.render( @@ -159,7 +160,7 @@ ReactDOM.render( ); ``` -由於 Node 端要到新版對於 ES6 支援較好,所以先用 `babel-register` 在 `src/server/index.js` 去即時轉譯 `server.js`,但不建議在 `production` 環境使用。 +Because Node-side needs latest version for better ES6 support, use `babel-register` in `src/server/index.js` to translate `server.js`, however this is not recommended for `production` environments. ```javascript // use babel-register to precompile ES6 @@ -168,7 +169,7 @@ require('./server'); ``` ```javascript -// 引入 Express、mongoose(MongoDB ORM)以及相關 server 上使用的套件 +// Import Express, mongoose (MongoDB ORM) and related serve packages /* Server Packages */ import Express from 'express'; import bodyParser from 'body-parser'; @@ -176,11 +177,11 @@ import cookieParser from 'cookie-parser'; import morgan from 'morgan'; import mongoose from 'mongoose'; import config from './config'; -// 引入後端 model 透過 model 和資料庫互動 +// import back-end model, communicate with database via model import User from './models/user'; import Recipe from './models/recipe'; -// 引入 webpackDevMiddleware 當做前端 server middleware +// import webpackDevMiddleware as server middleware for front-end /* Client Packages */ import webpack from 'webpack'; import React from 'react'; @@ -197,13 +198,13 @@ import configureStore from '../common/store/configureStore'; import fetchComponentData from '../common/utils/fetchComponentData'; import apiRoutes from './controllers/api.js'; /* config */ -// 初始化 Express server +// Initialize Express server const app = new Express(); const port = process.env.PORT || 3000; -// 連接到資料庫,相關設定檔案放在 config.database +// Connect to database, place related files in config.database mongoose.connect(config.database); // connect to database app.set('env', 'production'); -// 設定靜態檔案位置 +// Configure static file location app.use('/static', Express.static(__dirname + '/public')); app.use(cookieParser()); // use body parser so we can get info from POST and/or URL parameters @@ -212,7 +213,7 @@ app.use(bodyParser.json()); // use morgan to log requests to the console app.use(morgan('dev')); -// 負責每次接受到 request 的處理函數,判斷該如何處理和取得 initialState 整理後結合伺服器渲染頁面傳往前端 +// the function which handles every received request, discerns how to process and acquire initialState and sends integrated server UI rendering page to front-end const handleRender = (req, res) => { // Query our mock API asynchronously match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { @@ -245,7 +246,7 @@ const handleRender = (req, res) => { isEdit: false, } }); - // server side 渲染頁面 + // server side render page // Create a new Redux store instance const store = configureStore(initialState); const initView = renderToString( @@ -261,12 +262,12 @@ const handleRender = (req, res) => { }) } -// 基礎頁面 HTML 設計 +// Basic page HTML design const renderFullPage = (html, preloadedState) => (` - OpenCook 分享料理的美好時光 + OpenCook Share recipes and good times @@ -282,14 +283,14 @@ const renderFullPage = (html, preloadedState) => (` ` ); -// 設定 hot reload middleware +// Configure hot reload middleware const compiler = webpack(webpackConfig); app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: webpackConfig.output.publicPath })); app.use(webpackHotMiddleware(compiler)); -// 設計 API prefix,並使用 controller 中的 apiRoutes 進行處理 +// Design API prefix, and use controller apiRoutes to process app.use('/api', apiRoutes); -// 使用伺服器端 handleRender +// Using server-side handleRender app.use(handleRender); app.listen(port, (error) => { if (error) { @@ -300,7 +301,7 @@ app.listen(port, (error) => { }); ``` -由於 Node 端要到新版對於 ES6 支援較好,所以先用 `babel-register` 在 `src/server/index.js` 去即時轉譯 `server.js`,但目前不建議在 `production` 環境使用。 +Because Node-side needs latest version for better ES6 support, use `babel-register` in `src/server/index.js` to translate `server.js`, however this is not recommended for `production` environments. ```javascript // use babel-register to precompile ES6 syntax @@ -308,14 +309,14 @@ require('babel-register'); require('./server'); ``` -現在我們來設計一下我們資料庫的 Schema,在這邊我們使用 MongoDB 的 ORM Mongoose,可以方便我們使用物件方式進行資料庫的操作: +Now we design our database Schema, here we use a MongoDB ORM Mongoose, it allows for convenient object-oriented database operations: ```javascript -// 引入 mongoose 和 Schema +// Import mongoose and Schema import mongoose, { Schema } from 'mongoose'; -// 使用 mongoose.model 建立新的資料表,並將 Schema 傳入 -// 這邊我們設計了食譜分享的一些基本要素,包括名稱、描述、照片位置等 +// Use mongoose.model to create a new directory, and import Schema +// Here we designed the essential factors for recipe sharing, including name, description, photo path etc export default mongoose.model('Recipe', new Schema({ id: String, name: String, @@ -327,11 +328,11 @@ export default mongoose.model('Recipe', new Schema({ ``` ```javascript -// 引入 mongoose 和 Schema +// Import mongoose and Schema import mongoose, { Schema } from 'mongoose'; -// 使用 mongoose.model 建立新的資料表,並將 Schema 傳入 -// 這邊我們設計了使用者的一些基本要素,包括名稱、描述、照片位置等 +// Use mongoose.model to create a new table, importing Schema +// Here we designed some basic elements for users, including name, description, photo path etc export default mongoose.model('User', new Schema({ id: Number, username: String, @@ -341,13 +342,13 @@ export default mongoose.model('User', new Schema({ })); ``` -為了方便維護,我們把 API 的部份統一在 `src/server/controllers/api.js` 進行管理,這部份會涉及比較多 Node 和 mongoose 的操作,若讀者尚不熟悉可以參考 [mongoose 官網](http://mongoosejs.com/) +For convenient maintenance, we placed API in `src/server/controllers/api.js`,this part will use more Node and mongoose operations, if the user is not familar please refer to [mongoose official webpage](http://mongoosejs.com/) ```javascript import Express from 'express'; -// 引入 jsonwebtoken 套件 +// import jsonwebtoken package import jwt from 'jsonwebtoken'; -// 引入 User、Recipe Model 方便進行資料庫操作 +// import User, Recipe Model for convenient database operations import User from '../models/user'; import Recipe from '../models/recipe'; import config from '../config'; @@ -355,9 +356,9 @@ import config from '../config'; // API Route const app = new Express(); const apiRoutes = Express.Router(); -// 設定 JSON Web Token 的 secret variable +// configure secret variable for JSON Web Token app.set('superSecret', config.secret); // secret variable -// 使用者登入 API ,依據使用 email 和 密碼去驗證,若成功則回傳一個認證 token(時效24小時)我們把它存在 cookie 中,方便前後端存取。這邊我們先不考慮太多資訊安全的議題 +// User login API, validated with email and password, if successful returns a validation token (good for 24 hours) we save it in cookie, so it is convenient for front- and back-end to save and access. Here we will not make too many considerations for information security related topics apiRoutes.post('/login', function(req, res) { // find the user User.findOne({ @@ -377,7 +378,7 @@ apiRoutes.post('/login', function(req, res) { expiresIn: 60 * 60 * 24 // expires in 24 hours }); // return the information including token as JSON - // 若登入成功回傳一個 json 訊息 + // If successfully logged in return a json message res.json({ success: true, message: 'Enjoy your token!', @@ -388,7 +389,7 @@ apiRoutes.post('/login', function(req, res) { } }); }); -// 初始化 api,一開始資料庫尚未建立任何使用者,我們需要在瀏覽器輸入 `http://localhost:3000/api/setup`,進行資料庫初始化。這個動作將新增一個使用者、一份食譜,若是成功新增將回傳一個 success 訊息 +// Initialize api, at the start the database has not created any users, we must input `http://localhost:3000/api/setup` in our browser, to initialize our database. This action creates a new user, a recipe, and if sucessful returns a success message apiRoutes.get('/setup', (req, res) => { // create a sample user const sampleUser = new User({ @@ -399,10 +400,10 @@ apiRoutes.get('/setup', (req, res) => { }); const sampleRecipe = new Recipe({ id: '110ec58a-a0f2-4ac4-8393-c866d813b8d1', - name: '番茄炒蛋', - description: '番茄炒蛋,一道非常經典的家常菜料理。雖然看似普通,但每個家庭都有屬於自己家裡的不同味道', + name: 'tomato stir-fried with eggs', + description: 'tomato stir-fried with eggs, a classic household dish. Although it looks commonplace, every family gives an unique spin to the flavour', imagePath: 'https://c1.staticflickr.com/6/5011/5510599760_6668df5a8a_z.jpg', - steps: ['放入番茄', '打個蛋', '放入少許鹽巴', '用心快炒'], + steps: ['add tomatoes', 'beat an egg', 'add a touch of salt', 'diligently and swiftly stir-fry'], updatedAt: new Date() }); // save the sample user @@ -415,7 +416,7 @@ apiRoutes.get('/setup', (req, res) => { }) }); }); -// 回傳所有 recipes +// return all recipes apiRoutes.get('/recipes', (req, res) => { Recipe.find({}, (err, recipes) => { res.status(200).json(recipes); @@ -423,10 +424,10 @@ apiRoutes.get('/recipes', (req, res) => { }); // route middleware to verify a token -// 接下來的 api 將進行控管,也就是說必須在網址請求中夾帶認證 token 才能完成請求 +// next the api will handle access control, in other words the web request must include the validation token before the request is handled apiRoutes.use((req, res, next) => { // check header or url parameters or post parameters for token - // 確認標頭、網址或 post 參數是否含有 token,本範例因為簡便使用網址 query 參數 + // confirm headers, url or post parameters for token, this example uses query parameter for convenience var token = req.body.token || req.query.token || req.headers['x-access-token']; // decode token if (token) { @@ -449,20 +450,20 @@ apiRoutes.use((req, res, next) => { }); } }); -// 確認認證是否成功 +// confirm whether authentication succeeded apiRoutes.get('/authenticate', (req, res) => { res.json({ success: true, message: 'Enjoy your token!', }); }); -// create recipe 新增食譜 +// create recipe apiRoutes.post('/recipes', (req, res) => { const newRecipe = new Recipe({ name: req.body.name, description: req.body.description, imagePath: req.body.imagePath, - steps: ['放入番茄', '打個蛋', '放入少許鹽巴', '用心快炒'], + steps: ['add tomatoes', 'beat an egg', 'add a touch of salt', 'diligently and swiftly stir-fry'], updatedAt: new Date() }); newRecipe.save((err) => { @@ -471,13 +472,13 @@ apiRoutes.post('/recipes', (req, res) => { res.json({ success: true }); }); }); -// update recipe 根據 _id(mongodb 的 id)更新食譜 +// update recipe according to _id (mongodb id) apiRoutes.put('/recipes/:id', (req, res) => { Recipe.update({ _id: req.params.id }, { name: req.body.name, description: req.body.description, imagePath: req.body.imagePath, - steps: ['放入番茄', '打個蛋', '放入少許鹽巴', '用心快炒'], + steps: ['add tomatoes', 'beat an egg', 'add a touch of salt', 'diligently and swiftly stir-fry'], updatedAt: new Date() } ,(err) => { if (err) throw err; @@ -485,7 +486,7 @@ apiRoutes.put('/recipes/:id', (req, res) => { res.json({ success: true }); }); }); -// remove recipe 根據 _id 刪除食譜,若成功回傳成功訊息 +// remove recipe according to _id, if success return success message apiRoutes.delete('/recipes/:id', (req, res) => { Recipe.remove({ _id: req.params.id }, (err, recipe) => { if (err) throw err; @@ -496,7 +497,7 @@ apiRoutes.delete('/recipes/:id', (req, res) => { export default apiRoutes; ``` -設定整個 App 的 routing,我們主要頁面有 `HomePageContainer`、`LoginPageContainer`、`SharePageContainer`,值得注意的是我們這邊使用 [Higher Order Components](http://www.darul.io/post/2016-01-05_react-higher-order-components) (Higher Order Components 為一個函數, 接收一個 Component 後在 Class Component 的 render 中 return 回傳入的 components)方式去確認使用者是否有登入,若有沒登入則不能進入分享食譜頁面,反之若已登入也不會再進到登入頁面: +configure the routing for the entire App, our main page has `HomePageContainer`, `LoginPageContainer`, `SharePageContainer`, worth noting that here we use [Higher Order Components](http://www.darul.io/post/2016-01-05_react-higher-order-components) (Higher Order Components is a parameter where after receiving a Component, it is returned via the render method of the Class Component) method to confirm if a user has logged in, if not logged in they cannot go to the recipe sharing webpage, on the other hand if they have already logged in, they will no longer go to the login page: ```javascript import React from 'react'; @@ -516,7 +517,7 @@ export default ( ); ``` -設定行為常數(`src/constants/actionTypes.js`): +configure action constants (`src/constants/actionTypes.js`): ```javascript export const AUTH_START = "AUTH_START"; @@ -535,7 +536,7 @@ export const UPDATE_RECIPE = 'UPDATE_RECIPE'; export const DELETE_RECIPE = 'DELETE_RECIPE'; ``` -設定 `src/actions/recipeActions.js`,我們這邊使用 redux-promise,可以很容易使用非同步的行為 WebAPI: +Configure `src/actions/recipeActions.js`, here we use redux-promise, allowing us to easily use the asynchronously-behaving WebAPI: ```javascript import { createAction } from 'redux-actions'; @@ -556,7 +557,7 @@ export const deleteRecipe = createAction('DELETE_RECIPE', WebAPI.deleteRecipe); export const setRecipe = createAction('SET_RECIPE'); ``` -設定 `src/actions/uiActions.js`: +Configure `src/actions/uiActions.js`: ```javascript import { createAction } from 'redux-actions'; @@ -573,7 +574,7 @@ export const hideSpinner = createAction('HIDE_SPINNER'); export const setUi = createAction('SET_UI'); ``` -設定 `src/actions/userActions.js`,處理使用者登入登出等行為: +Configure `src/actions/userActions.js`, handle user login and log out behaviour: ```javascript import { createAction } from 'redux-actions'; @@ -596,7 +597,7 @@ export const checkAuth = createAction('CHECK_AUTH'); export const setUser = createAction('SET_USER'); ``` -於 `scr/actions/index.js` 輸出 actions: +From `scr/actions/index.js` export actions: ```javascript export * from './userActions'; @@ -604,24 +605,24 @@ export * from './recipeActions'; export * from './uiActions'; ``` -於 `scr/common/utils/fetchComponentData.js` 設定 server side 初始 fetchComponentData: +From `scr/common/utils/fetchComponentData.js` configure server side initial fetchComponentData: ```javascript -// 這邊使用 axios 方便進行 promises base request +// here we use axios for convenient promises base requests import axios from 'axios'; -// 記得附加上我們存在 cookies 的 token +// remember to add the token we saved in cookies export default function fetchComponentData(token = 'token') { const promises = [axios.get('http://localhost:3000/api/recipes'), axios.get('http://localhost:3000/api/authenticate?token=' + token)]; return Promise.all(promises); } ``` -於 `scr/common/utils/WebAPI.js` 所有前端 API 的處理: +from `scr/common/utils/WebAPI.js` all front-end API handling: ```javascript import axios from 'axios'; import { browserHistory } from 'react-router'; -// 引入 uuid 當做食譜 id +// import uuid as recipe id import uuid from 'uuid'; import { @@ -631,7 +632,7 @@ import { completeLogout, } from '../actions'; -// getCookie 函數傳入 key 回傳 value +// feed key parameter into getCookie and return value function getCookie(keyName) { var name = keyName + '='; const cookies = document.cookie.split(';'); @@ -648,7 +649,7 @@ function getCookie(keyName) { } export default { - // 呼叫後端登入 api + // call backend login api login: (dispatch, email, password) => { axios.post('/api/login', { email: email, @@ -658,7 +659,7 @@ export default { if(response.data.success === false) { dispatch(authError()); dispatch(hideSpinner()); - alert('發生錯誤,請再試一次!'); + alert('Something went wrong, please try again!'); window.location.reload(); } else { if (!document.cookie.token) { @@ -676,13 +677,13 @@ export default { dispatch(authError()); }); }, - // 呼叫後端登出 api + // call backend logout api logout: (dispatch) => { document.cookie = 'token=; ' + 'expires=Thu, 01 Jan 1970 00:00:01 GMT;'; dispatch(hideSpinner()); browserHistory.push('/'); }, - // 確認使用者是否登入 + // confirm whether user is logged in checkAuth: (dispatch, token) => { axios.post('/api/authenticate', { token: token, @@ -698,7 +699,7 @@ export default { dispatch(authError()); }); }, - // 取得目前所有食譜 + // get all current recipes getRecipes: () => { axios.get('/api/recipes') .then((response) => { @@ -706,7 +707,7 @@ export default { .catch((error) => { }); }, - // 呼叫新增食譜 api,記得附加上我們存在 cookies 的 token + // call the recipe creation api, remember to add the token saved in our cookies addRecipe: (dispatch, name, description, imagePath) => { const id = uuid.v4(); axios.post('/api/recipes?token=' + getCookie('token'), { @@ -718,7 +719,7 @@ export default { .then((response) => { if(response.data.success === false) { dispatch(hideSpinner()); - alert('發生錯誤,請再試一次!'); + alert('Something went wrong, please try again!'); browserHistory.push('/share'); } else { dispatch(hideSpinner()); @@ -729,7 +730,7 @@ export default { .catch(function (error) { }); }, - // 呼叫更新食譜 api,記得附加上我們存在 cookies 的 token + // call update recipe remember to add the token we saved in cookies updateRecipe: (dispatch, recipeId, name, description, imagePath) => { axios.put('/api/recipes/' + recipeId + '?token=' + getCookie('token'), { id: recipeId, @@ -742,7 +743,7 @@ export default { dispatch(hideSpinner()); dispatch(setRecipe({ key: 'recipeId', value: '' })); dispatch(setUi({ key: 'isEdit', value: false })); - alert('發生錯誤,請再試一次!'); + alert('Something went wrong, please try again!'); browserHistory.push('/share'); } else { dispatch(hideSpinner()); @@ -753,13 +754,13 @@ export default { .catch(function (error) { }); }, - // 呼叫刪除食譜 api,記得附加上我們存在 cookies 的 token + // call recipe deletion api, remember to add the token we saved in cookies deleteRecipe: (dispatch, recipeId) => { axios.delete('/api/recipes/' + recipeId + '?token=' + getCookie('token')) .then((response) => { if(response.data.success === false) { dispatch(hideSpinner()); - alert('發生錯誤,請再試一次!'); + alert('Something went wrong, please try again!'); browserHistory.push('/'); } else { dispatch(hideSpinner()); @@ -773,7 +774,7 @@ export default { }; ``` -接下來設定我們的 `reducers`,以下是 `src/common/reducers/data/recipeReducers.js`,`GET_RECIPES` 負責將後端 API 取得的所有食譜存放在 recipes 中: +Next configure our `reducers`, below is `src/common/reducers/data/recipeReducers.js`, `GET_RECIPES` is responsible for getting all recipes from the server and placing it in `recipes`: ```javascript import { handleActions } from 'redux-actions'; @@ -799,7 +800,7 @@ const recipeReducers = handleActions({ export default recipeReducers; ``` -以下是 `src/common/reducers/data/userReducers.js`,負責確認登入相關處理事項。注意的是由於登入是非同步執行,所以會有幾個階段的行為要做處理: +Below is `src/common/reducers/data/userReducers.js`, responsible for confirming login related handling tasks, pay note because login is asynchronously executed, there are several stages of behaviours to handle: ```javascript import { handleActions } from 'redux-actions'; @@ -851,7 +852,7 @@ export default userReducers; ``` -以下是 `src/common/reducers/ui/uiReducers.js`,負責確認 UI State 相關處理: +Below is `src/common/reducers/ui/uiReducers.js`, responsible for confirming UI State related tasks: ```javascript @@ -885,7 +886,7 @@ const uiReducers = handleActions({ export default uiReducers; ``` -最後把所有 recipes 在 `src/common/reducers/index.js` 使用 `combineReducers` 整合在一起,注意的是我們整個 App 的資料流要維持 immutable: +At last assemble all recipes with `combineReducers` in `src/common/reducers/index.js`, pay note our entire App data flow must remain immutable: ```javascript import { combineReducers } from 'redux-immutable'; @@ -903,7 +904,7 @@ const rootReducer = combineReducers({ export default rootReducer; ``` -以下是 `src/common/store/configureStore.js` 處理 store 的建立,這次我們使用了 `promiseMiddleware` 的 middleware: +Below is `src/common/store/configureStore.js` responsible for store configuration, this time we used middleware from `promiseMiddleware`: ```javascript import { createStore, applyMiddleware } from 'redux'; @@ -925,7 +926,7 @@ export default function configureStore(preloadedState = initialState) { } ``` -經過一連串努力,我們來到了 View 的佈建。在這個 App 中我們主要會由一個 AppBar 負責所有頁面的導覽,也就是每個頁面都會有 AppBar 常駐在上面,然而上面的內容則會依 UI State 中的 isAuthorized 而有所不同。最後要留意的是我們使用了 React Bootstrapt 來建立 React Component。 +After a series of efforts, we come to the decorating of View. In this App we will mainly use one AppBar for page navigation, in other words every page will have an Appbar stationed at the top, however the content will differ according to isAuthorized in UI State. Lastly we should pay note that we used React Bootstrap to establish React Component. ```javascript import React from 'react'; @@ -950,13 +951,13 @@ const AppBar = ({ isAuthorized === false ? ( ) : ( ) } @@ -967,7 +968,7 @@ const AppBar = ({ export default AppBar; ``` -以下是 `src/common/containers/AppBarContainer/AppBarContainer.js`: +Below is `src/common/containers/AppBarContainer/AppBarContainer.js`: ```javascript import React from 'react'; @@ -999,7 +1000,7 @@ export default connect( )(AppBar); ``` -以下是 `src/components/Main/Main.js`,透過 route 機制讓 AppBarContainer 可以成為整個 App 母模版: +Below is `src/components/Main/Main.js`, via route mechanism we allow AppBarContainer to become the parent template for the entire App: ```javascript import React from 'react'; @@ -1017,7 +1018,7 @@ const Main = (props) => ( export default Main; ``` - 在 `checkAuth` 這個 Component 中,我們使用到了 Higher Order Components 的觀念。Higher Order Components 為一個函數, 接收一個 Component 後在 Class Component 的 render 中 return 回傳入的 components 方式去確認使用者是否有登入,若有沒登入則不能進入分享食譜頁面,反之若已登入也不會再進到登入頁面: +Within the `checkAuth` Component, we used the Higher Order Components concept. Higher Order Components is a parameter where after receiving a Component it is returned from render method of Class Component thereby confirming if the user has logged in, if they are not they may not enter the recipe sharing page, on the other hand if they have logged in they will no longer enter the login page: ```javascript import React from 'react'; @@ -1063,7 +1064,7 @@ export default function requireAuthentication(Component, type) { } ``` -我們將每個食譜呈現設計成 RecipeBox,以下是在 `src/common/components/HomePage/HomePage.js` 使用 map 方法去迭代我們的食譜: +We designed the presentation of each recipe as a RecipeBox, below is `src/common/components/HomePage/HomePage.js` using map method to iterate over our recipes: ```javascript import React from 'react'; @@ -1084,7 +1085,7 @@ const HomePage = ({ export default HomePage; ``` -以下是 `src/common/containers/HomePageContainer/HomePageContainer.js`: +Below is `src/common/containers/HomePageContainer/HomePageContainer.js`: ```javascript @@ -1101,7 +1102,7 @@ export default connect( )(HomePage); ``` -在 `src/common/components/LoginBox/LoginBox.js` 設計我們 LoginBox: +In `src/common/components/LoginBox/LoginBox.js` design our LoginBox: ```javascript import React from 'react'; @@ -1119,7 +1120,7 @@ const LoginBox = ({ - 請輸入您的 Email + Please input your Email - 請輸入您的密碼 + Please input your password - 提交送出 + submit @@ -1153,7 +1154,7 @@ const LoginBox = ({ export default LoginBox; ``` -以下是 `src/common/containers/LoginBoxContainer/LoginBoxContainer.js`: +Below is `src/common/containers/LoginBoxContainer/LoginBoxContainer.js`: ```javascript @@ -1195,7 +1196,7 @@ export default connect( ``` -在 `src/common/components/LoginPage/LoginPage.js`,當 spinnerVisible 為 true 會顯示 spinner: +In `src/common/components/LoginPage/LoginPage.js`, when spinnerVisible is true the spinner is displayed: ```javascript @@ -1222,7 +1223,7 @@ const LoginPage = ({ export default LoginPage; ``` -以下是 `src/common/containers/LoginPageContainer/LoginPageContainer.js`: +Below is `src/common/containers/LoginPageContainer/LoginPageContainer.js`: ``` @@ -1241,7 +1242,7 @@ export default connect( ``` -真正設計我們內部的食譜, `src/common/components/RecipeBox`,使用者登入的話可以修改和刪除食譜: +Actually designing our recipes, `src/common/components/RecipeBox`, if the user is signed in they can edit and delete recipes: ```javascript import React from 'react'; @@ -1256,8 +1257,8 @@ const RecipeBox = (props) => { { props.isAuthorized === true ? (

    -   - +   +

    ) : null } @@ -1269,7 +1270,7 @@ const RecipeBox = (props) => { export default RecipeBox; ``` -以下是 `src/common/containers/RecipeBoxContainer/RecipeBoxContainer.js`: +Below is `src/common/containers/RecipeBoxContainer/RecipeBoxContainer.js`: ```javascript @@ -1315,7 +1316,7 @@ export default connect( ``` -設計我們分享食譜頁面,這邊我們把編輯食譜和新增分享一起共用了同一個 components,差別在於我們會判斷 UI State 中的 `isEdit`, 決定相應處理方式。在中 `src/common/components/ShareBox/ShareBox.js`,可以讓使用者登入的後修改和刪除食譜: +Design our recipe sharing page, here we used the same component for editing and adding a new recipe, the difference is we decide based on `isEdit` in UI State which corresponding handling method to use. In `src/common/components/ShareBox/ShareBox.js`, we allow users to edit and delete recipes after signin: ```javascript @@ -1328,7 +1329,7 @@ const ShareBox = (props) => { - 請輸入食譜名稱 + Please input recipe name { - 請輸入食譜說明 + Please input recipe description { - 請輸入食譜圖片網址 + Please input recipe image url { bsSize="large" block > - 提交送出 + submit ); @@ -1376,7 +1377,7 @@ const ShareBox = (props) => { export default ShareBox; ``` -以下是 `src/common/containers/ShareBoxContainer/ShareBoxContainer.js`: +Below is `src/common/containers/ShareBoxContainer/ShareBoxContainer.js`: ```javascript @@ -1432,7 +1433,7 @@ export default connect( ``` -單純的 SharePage(`src/common/components/SharePage/SharePage.js`)頁面: +Simple SharePage (`src/common/components/SharePage/SharePage.js`) Page: ```javascript import React from 'react'; @@ -1452,7 +1453,7 @@ const SharePage = () => ( export default SharePage; ``` -以下是 `src/common/containers/SharePageContainer/SharePageContainer.js`: +Below is `src/common/containers/SharePageContainer/SharePageContainer.js`: ```javascript @@ -1468,19 +1469,19 @@ export default connect( )(SharePage); ``` -恭喜你成功抵達終點!若一切順利,在終端機打上 `$ npm start`,你將可以在瀏覽器的 `http://localhost:3000` 看到自己的成果! +Congrats for crossing the finish line sucessfully! If everything went well, in the terminal run `$ npm start`, and you can see the fruits of your effort in the browser at `http://localhost:3000`! -![用 React + Redux + Node(Isomorphic)開發一個食譜分享網站](./images/open-cook-demo-1.png "用 React + Redux + Node(Isomorphic)開發一個食譜分享網站") +![Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](./images/open-cook-demo-1.png "Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website") -## 總結 -本章整合過去所學和添加一些後端資料庫知識開發了一個可以登入會員並分享食譜的社群網站!快把你的成果和你的朋友分享吧!覺得意猶未盡?別忘了附錄也很精采!最後,再次謝謝讀者們支持我們一路走完了 React 開發學習之旅!然而前端技術變化很快,唯有不斷自我學習才能持續成長。筆者才疏學淺,撰寫學習心得或有疏漏,若有任何建議或提醒都歡迎和我說,大家一起加油:) +## Summary +This article integrated all of our past learnings and added a bit of backend database knowledge to develop a community website that can sign members in and allow recipe-sharing! Hurry up and share your result with your friends! Yearning for more? Don't forget the Appendix is also very exciting! Lastly, once again many thanks to readers that have supported our path all the way to completing this React development journey! However front-end technologies change rapidly, only continuous self driven learning will sustain growth. This writer is of humble talent and shallow learning, if there are any insights while learning, missed points, suggestions or reminders I welcome everyone to tell me, let us work hard together :) -## 延伸閱讀 +## Extended Reading 1. [joshgeller/react-redux-jwt-auth-example](https://github.com/joshgeller/react-redux-jwt-auth-example) 2. [Securing React Redux Apps With JWT Tokens](https://medium.com/@rajaraodv/securing-react-redux-apps-with-jwt-tokens-fcfe81356ea0#.5hfri5j5m) 3. [Adding Authentication to Your React Native App Using JSON Web Tokens](https://auth0.com/blog/adding-authentication-to-react-native-using-jwt/) 4. [Authentication in React Applications, Part 2: JSON Web Token (JWT)](http://vladimirponomarev.com/blog/authentication-in-react-apps-jwt) -5. [Node.js 身份認證:Passport 入門](https://nodejust.com/nodejs-passport-auth-tutorial/) +5. [Node.js id validation: Passport primer](https://nodejust.com/nodejs-passport-auth-tutorial/) 6. [react-bootstrap compatibility #83](https://github.com/reactjs/react-router/issues/83) 7. [How to authenticate routes using Passport? #725](https://github.com/reactjs/react-router/issues/725) 8. [Isomorphic React Web App Demo with Material UI](https://github.com/tech-dojo/react-showcase) @@ -1502,7 +1503,7 @@ export default connect( ## License MIT, Special thanks [Loading.io](http://loading.io/) -## :door: 任意門 -| [回首頁](https://github.com/kdchang/reactjs101) | [上一章:React Redux Sever Rendering(Isomorphic JavaScript)入門](https://github.com/kdchang/reactjs101/blob/master/Ch10/react-redux-server-rendering-isomorphic-javascript.md) | [下一章:附錄一、React ES5、ES6+ 常見用法對照表](https://github.com/kdchang/reactjs101/tree/master/Appendix01) | +## :door: Nexus +| [Home](https://github.com/sycherng/reactjs101/tree/en-US) | [Previous article: React Redux Sever Rendering (Isomorphic JavaScript) Introduction](https://github.com/sycherng/reactjs101/blob/en-US/Ch10/react-redux-server-rendering-isomorphic-javascript.md) | [Next article: Appendix 1, React ES5, ES6+ Common Usage Reference Sheet](https://github.com/sycherng/reactjs101/tree/en-US/Appendix01) | -| [勘誤、提問或許願](https://github.com/kdchang/reactjs101/issues) | +| [Corrections, questions or requests](https://github.com/kdchang/reactjs101/issues) | diff --git a/README.md b/README.md index 02dbcc2..6449699 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,59 @@ -# 從零開始學 ReactJS(ReactJS 101) -一本給初學者的 React 中文入門教學書,由淺入深學習 ReactJS 生態系 (Flux, Redux, React Router, ImmutableJS, React Native, Relay/GraphQL etc.),打造跨平台應用程式。 +# ReactJS 101 (lit. Learn ReactJS From Zero) +An introductory educational book for ReactJS, originally written in Traditional Mandarin Chinese, that hopes to enable beginners to learn ReactJS on first glance, through covering the ReactJS ecosystem from simple to complex (Flux, Redux, React Router, ImmutableJS, React Native, Relay/GraphQL etc). -![從零開始學 ReactJS(ReactJS 101)](./cover.png) +![ReactJS 101](./cover.png) -## 相關連結(Links) +## Links -1. [從零開始學 ReactJS(ReactJS 101)粉絲頁](https://www.facebook.com/reactjs101/) +1. [ReactJS 101 fanpage](https://www.facebook.com/reactjs101/) -2. [繁體中文範例程式碼和書籍內容連載位置](https://github.com/kdchang/reactjs101) +2. [Traditional Chinese Code Demo and Book Content Source](https://github.com/kdchang/reactjs101) -3. [勘誤、許願、建議或提問](https://github.com/kdchang/reactjs101/issues) +3. [Corrections, questions, or requests](https://github.com/kdchang/reactjs101/issues) -## 翻譯版本(Translate) +## Translated versions -1. [简体中文版本 by @carlleton](https://github.com/carlleton/reactjs101/tree/zh-CN) -2. [前端圈简体中文版本 by @blueflylin]( https://github.com/blueflylin/reactjs101) [特別感謝前端圈小夥伴!](http://fequan.com/) +1. [简体中文版本 Simplified Chinese version by @carlleton](https://github.com/carlleton/reactjs101/tree/zh-CN) +2. [前端圈简体中文版本 Fequan Simplified Chinese version by @blueflylin]( https://github.com/blueflylin/reactjs101) [special thanks to our buddies at Fequen!](http://fequan.com/) +3. [英文版本 English version by @sycherng](https://github.com/sycherng/reactjs101/tree/en-US) -若需翻譯成其他語言版本,請先 `fork` 一份 `repo` 到自己的 GitHub 並另外開新的 `branch`。最後將翻譯版本連結更新在 `master` 分支中 `README.md` 的 `相關連結(Links)` 後發送 `Pull Request`,謝謝您。 +If you wish to translate to other languages, please first `fork` a copy of the `repo` to your own Github and start a new `branch`. At the end update the link for your translated book within `master` folder's `README.md` and `Links` and send a `Pull Request`, thank you. -## 目錄(Table of Contents) -- [X] [一、前端工程和 React 生態系(Ecosystem)簡介](https://github.com/kdchang/reactjs101/tree/master/Ch01) -- [X] [二、開發環境設置與 Webpack 入門](https://github.com/kdchang/reactjs101/tree/master/Ch02) -- [X] [三、React/JSX/Component 簡介](https://github.com/kdchang/reactjs101/tree/master/Ch03) -- [X] [四、Props/State 基礎與 Component 生命週期](https://github.com/kdchang/reactjs101/tree/master/Ch04) -- [X] [五、React Router](https://github.com/kdchang/reactjs101/tree/master/Ch05) -- [X] [六、ImmutableJS](https://github.com/kdchang/reactjs101/tree/master/Ch06) -- [X] [七、Flux/Redux](https://github.com/kdchang/reactjs101/tree/master/Ch07) -- [X] [八、Container 與 Presentational Components](https://github.com/kdchang/reactjs101/tree/master/Ch08) -- [X] [九、實戰教學:用 React + Router + Redux + ImmutableJS 寫一個 Github 查詢應用](https://github.com/kdchang/reactjs101/tree/master/Ch09) -- [X] [十、實戰教學:用 React + Redux + Node(Isomorphic JavaScript)開發食譜分享網站](https://github.com/kdchang/reactjs101/tree/master/Ch10) -- [X] [附錄一、React ES5、ES6+ 常見用法對照表](https://github.com/kdchang/reactjs101/tree/master/Appendix01) -- [X] [附錄二、用 React Native + Firebase 開發跨平台行動應用程式(Native Mobile App)](https://github.com/kdchang/reactjs101/tree/master/Appendix02) -- [X] [附錄三、React 測試入門教學](https://github.com/kdchang/reactjs101/tree/master/Appendix03) -- [X] [附錄四、GraphQL/Relay 初體驗](https://github.com/kdchang/reactjs101/tree/master/Appendix04) +## Table of Contents -## 先備知識(Prior Knowledge) -本書針對已具備基本 HTML、CSS 和 JavaScript 和 DOM 操作知識的讀者設計,但若讀者對上述的技術仍不熟悉的話,建議可以先行參考:[MDN](https://developer.mozilla.org/zh-TW/)、[Codecademy](https://www.codecademy.com/)、[W3C School](http://www.w3schools.com/)、[JavaScript核心](http://weizhifeng.net/javascript-the-core.html) 或是參考筆者 [之前的教學講義](http://kdchang.cc/web-programming-course/) 進行學習。另外,本書全書範例都將以 ES6+ 撰寫,若需參考 ES5 用法,請參考附錄一的 [React ES5、ES6+ 常見用法對照表](https://github.com/kdchang/reactjs101/tree/master/Appendix01)。 +- [X] [1. Web Front-End Development Introduction and React Ecosystem Introduction](https://github.com/sycherng/reactjs101/tree/en-US/Ch01) +- [X] [2. React Development Environment Settings and Introduction to Webpack](https://github.com/sycherng/reactjs101/tree/en-US/Ch02) +- [X] [3. React/JSX/Component Introduction](https://github.com/sycherng/reactjs101/tree/en-US/Ch03) +- [X] [4. Props/State Basics and Component Lifecycles](https://github.com/sycherng/reactjs101/tree/en-US/Ch04) +- [X] [5. React Router](https://github.com/sycherng/reactjs101/tree/en-US/Ch05) +- [X] [6. ImmutableJS](https://github.com/sycherng/reactjs101/tree/en-US/Ch06) +- [X] [7. Flux/Redux](https://github.com/sycherng/reactjs101/tree/en-US/Ch07) +- [X] [8. Container and Presentational Components](https://github.com/sycherng/reactjs101/tree/en-US/Ch08) +- [X] [9. Learn by Writing: Using React + Router + Redux + ImmutableJS to write a Github search app](https://github.com/sycherng/reactjs101/tree/en-US/Ch09) +- [X] [10. Learn by Writing: Using React + Redux + Node (Isomorphic JavaScript) to develop a recipe-sharing website](https://github.com/sycherng/reactjs101/tree/en-US/Ch10) +- [X] [Appendix 1. React ES5, ES6+ Common Usage Reference Sheet](https://github.com/sycherng/reactjs101/tree/en-US/Appendix01) +- [X] [Appendix 2. Using React Native + Firebase to develop a cross-platform native mobile app](https://github.com/sycherng/reactjs101/tree/en-US/Appendix02) +- [X] [Appendix 3. React Testing Introduction](https://github.com/sycherng/reactjs101/tree/en-US/Appendix03) +- [X] [Appendix 4. GraphQL/Relay First Experience](https://github.com/sycherng/reactjs101/tree/en-US/Appendix04) -## 關於作者(Author) -[@kdchang](http://blog.kdchang.cc) 文藝型開發者,夢想是做出人們想用的產品和辦一所心目中理想的學校,目前專注在 Mobile 和 IoT 應用開發。A Starter & Maker. JavaScript, Python & Arduino/Android lover.:) +## Prerequisite Knowledge +This book was designed for readers who have fundamental skills in HTML, CSS, and Javascript, as well as experience working with the Document-Object Model (DOM), however if the reader is not entirely familiar with the above skills, I recommend first reading: [MDN](https://developer.mozilla.org/en-US/), [Codecademy](https://www.codecademy.com/), [W3C School](http://www.w3schools.com/), [JavaScript Core](http://weizhifeng.net/javascript-the-core.html) or my [previous courses](http://kdchang.cc/web-programming-course/) for learning. Furthermore, this book uses ES6+ for all of its demos, if you need a comparison sheet to ES5 usage, please refer to Appendix 1 [React ES5, ES6+ Common Usage Reference Sheet](https://github.com/sycherng/reactjs101/tree/en-US/Appendix01). -## 版權許可(License) -本書採用創用CC授權4.0 "姓名標示─非商業性─相同方式分享(BY-NC-SA)" 授權。 +## Author +[@kdchang](http://blog.kdchang.cc) is an artistic developer, who dreams to create products people want to use as well as establish an ideal [software] academy, currently focused on Mobile and IoT application development. A Starter & Maker, JavaScript, Python & Arduino/Android lover.:) -![從零開始學 ReactJS(ReactJS 101)](./cc-by-nc-sa.png) +## License +This book uses Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) license. -本授權條款允許使用者重製、散布、傳輸以及修改著作,但不得為商業目的之使用。若使用者修改該著作時,僅得依本授權條款或與本授權條款類似者來散布該衍生作品。使用時必須按照著作人指定的方式表彰其姓名。 +![ReactJS 101](./cc-by-nc-sa.png) -詳細資訊請參考 [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)。 +This license permits users to copy and redistribute the material as well as adapt the material, but prohibits commercial use. +If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. +During use you must follow the author's preferred way of writing their name. -## 關鍵字(Keywords) -React, React Native, React Router, Flux, Redux, Node, Express, ImmutableJS, NPM, Babel, Browserify, Webpack, Gulp, Grunt, Pure Functions, PropTypes, Stateless Functional Components, Presentational Components, ES6, ES5, JSX, Jest, Unit Test, Component, Relay, GraphQL, Universal/Isomorphic, React Tutorial React教程, React教學, 學React, React Tutorial, Tutorial, Ecosystem, Front-End +For detailed information please refer to [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/). + +## Keywords +React, React Native, React Router, Flux, Redux, Node, Express, ImmutableJS, NPM, Babel, Browserify, Webpack, Gulp, Grunt, Pure Functions, PropTypes, Stateless Functional Components, Presentational Components, ES6, ES5, JSX, Jest, Unit Test, Component, Relay, GraphQL, Universal/Isomorphic, React Tutorial, React Course, React Education, Learn React, React Tutorial, Tutorial, Ecosystem, Front-End