ttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
The smallest React example looks like this:
ReactDOM.render(
- <h1>Hello, world!</h1>,
- document.getElementById('root')
- );
介紹JSX
a syntax extension to JavaScript.
const element = <h1>hello, world</h1>;
這不是string也不是HTML,這是JSX。是JS語法的擴充套件。
用途:在UI和JS程式碼混合工作時,作為視覺化的助手 ;另外,JSX 讓React 顯示很多有用的錯誤和警告資訊。
1.Embedding Expressions in JSX .在JSX中插入JS表示式,用{}括起來。
2.JSX 本身也是Expression.。 可以把JSX用在if,for, 分配給變數,作為引數,和從函式返回。
3.可以用字串作為屬性值,可以用{}插入JS expression給屬性賦值。
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>
JSX更像是JavaScript,所以屬性要用駝峰寫法class-> className
4.如果tag是空的,可以用簡寫法:
const element = <img src={user.avatarUrl} />;
5.JSX tags may contain children:就是可以巢狀
6.React.createElement()方法,相等。這個物件稱為 React element.
Rendering Elements
React element 是 components的組成部分。
假設有<div id="root"></div>,使用ReactDOM.render操作DOM節點:
相當於JS中的document.getElementById("root").innerHTML = element;
React element是惰性的,不能改它的屬性和孩子。
因為用setInterval函式每秒呼叫一次tick函式,同時element內部的程式碼new Date(),所以React element元素更新了。
Components and Props
元件比較像JS function,接受輸入( props舞臺道具 )並返回React elements
最簡單定義一個component:使用JS函式。(相對簡潔)
這個函式是一個React component,因為它接收了一個props object argument 作為資料並返回一個React element
也可以使用ES6 class來定義一個元件:
Rendering a Component
const element = <div />;
這個React element代表Dom tag.⚠️首字母用小寫,程式碼DOM tags.
另外,也可以element也可以代表自定義元件,⚠️首字母用大寫,代表元件。
const element = <Welcome name="Sara" /> ;
當這麼用時,呼叫Welcome元件,並傳遞JSX屬性給這個元件,這個屬性是一個object,稱為object"props",最後返回元件的結果。
例子:
過程:
- We call
ReactDOM.render()
with the<Welcome name="Sara" />
element. - React calls the
Welcome
component with{name: 'Sara'}
as the props. - Our
Welcome
component returns a<h1>Hello, Sara</h1>
element as the result. - React DOM efficiently updates the DOM to match
<h1>Hello, Sara</h1>
.
元件可以在它們的輸出中加入別的元件
例子:
Extracting Components
如果一個元件過於複雜,或者一個元件可以在程式中多次使用,那麼可以把它提取出來。類似於Ruby中的dry原則。
Props are Read-Only
State and Lifecycle
State類似於props,但是是私有的,只能被元件控制。
當元件使用class來定義時增加了一些新的features,其中Local state就是其中之一
Converting a Function to a Class
- Create an class, with the same name, that extends React.Component.
- Add a single empty method to it called render().
- Move the body of the function into the render() method.
- Replace props with this.props in the render() body.
- Delete the remaining empty function declaration.
例子:https://codepen.io/gaearon/pen/zKRGpo?editors=0010
function tick() {
ReactDOM.render(
Adding Local State to a Class
3步:
- Replace this.props.date with this.state.date in the render() method;
- Add a class constructor that assigns the initial this.state:
constructor 方法是一個特殊的方法用來創造和初始化一個物件伴隨著一個類。因此這個類中只能有這麼一個constructor方法。
constructor使用super關鍵字來呼叫自己父類的constructor.
3. Remove the date prop from the <Clock />element. 並去掉剩餘函式
結果:
Adding Lifecycle Methods to a Class
在元件類中定義特殊的方法componentDidMount(),componentWillUnmount(),當一個元件載入mounting,和解除安裝unmounting時。
這兩個方法叫lifecycle hooks。類似於Ruby中的after_action。鉤子方法。
setInterval(): 每隔指定的時間後,呼叫一次“函式或表示式”。直到clearInterval()被呼叫或者瀏覽器視窗關閉。1000ms =1 second; (詳細解說)
() => {};箭頭函式:arrow function
ES6引用,目的是讓程式碼,更簡短並且不建立自己的this。因為傳統函式一旦新定義就會有自己的this,如果是一個函式巢狀一個新定義的函式,this就會弄混作用域。而箭頭函式不建立自己的this,會使用上下文環境的this值。
(param..) => {statements}
(param..) => expression
//等同(param..) => {return epression; }
//Parenteses are optional when there is only one parameter name:
singleParam => {statements}
//支援引數列表解構
let f = ([a, b] = [1, 2], {x: c} = {x: a + b}) => a + b + c;
f(); // 6
//沒有引數則需要用(), ()=> {}
//Parenthesize the body of function to return an object literal expression:
params => ({foo: bar})
fun執行時,把bind裡的this的值傳給它。fun本身的this的值被替換。
分析:
- <Clock />作為引數被傳入ReactDOM.render()後,React呼叫Clock元件的constructor方法。Clock需要顯示當前時間,所以它初始化了this.state,一個包含當前時間的JS物件。之後將更新這個物件,this.state。
- 然後,React呼叫Clock元件的render()方法。更新DOM以匹配元件的渲染輸出。
- 當元件Clock的輸出插入到網頁對應的DOM後,React 呼叫 lifecycle鉤子方法componentDidMount()。在這個方法內部,元件Clock要瀏覽器建立a timer來每秒呼叫一次元件的tick()方法。
- 每秒瀏覽器呼叫tick方法。在tick內部,Clock元件呼叫setState()方法,更新this.state物件中的當前時間。
- 由setState()方法,React可以知道this.state已經發生變化,需要再次呼叫render方法。因此瀏覽器頁面上的時間發生變化。
- 如果Clock元件要從DOM移走,React呼叫componentWillUnmount()鉤子方法。
⚠️ :在第一次呼叫Clock的人的人方法後,首次載入componentDidmount()方法,從英文名字Did可以看出,元件did後mount。setState和 render一前一後執行。
Handing Evenets處理事件
React events 使用駝峰命名
With JSx you pass a function as the event handler,rather than a string.
兩種方法繫結上下文this:
傳遞引數給Event Handlers
二選一:
e引數代表了React event,作為第二個引數。
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
條件渲染Conditional Rendering
if 和JS相似;
反覆練習:
全的案例:https://codepen.io/gaearon/pen/QKzAgB?editors=0010
程式碼少但是功能都有:https://codepen.io/gaearon/pen/Xjoqwm?editors=0010
handleLoginClick = () => {}
List and Keys
Basic List Component:
這組程式碼在嚴格模式下Console會彈出⚠️,
- "Warning: Each child in an array or iterator should have a unique 'key' prop.
因為<li> 沒有加上key關鍵字,類似於id。唯一識別碼,用於改變和增減。
改為: <li key={number.toString()}>,⚠️key應該是唯一的,這裡是唯一的。
規則:在map()裡的element加上key.
規則:最好的key是唯一的識別碼 ,在同等級的Siblings中。
Forms
HTMl from elements有自然的內部state,它有預設的behavior提交表格時定位到新的頁面。React也支援這個行為,但是如果用JS function來處理表格的提交和存取資料會非常convenitent。 這種實現技術稱為 controlled components.
Controlled Components
在HTML如<input> <textarea> <select>基於使用者的輸入來儲存或更新它們的state。在React, 易變的state儲存在元件的state property中,更新則使用setState().
案例:https://codepen.io/gaearon/pen/VmmPgp?editors=0010
疑問:
1.event.target.value是什麼意思?
event是一個虛擬的事件。React定義這些synthetic events用來對應 W3C(event)標準。
個人理解:DOMEventTarget target
這是SyntheticEvent object的attributes >見擴充套件,每個物件有14個屬性(其中4個方法)。
擴充套件:SyntheticEvent: https://reactjs.org/docs/events.html
我們的事件處理會被傳到SyntheticEvent的例項,一個關於瀏覽器原生事件的跨瀏覽器包裹器wrapper。和原生瀏覽器事件的互動是一樣的,包括stopPropagation()和preventDefault().
2.event.prevetnDefault();
答案:這也是SyntheticEvent例項物件的屬性,一個方法,React不能使用 return false來防止預設的行為,如防止預設開啟一個link到新的網頁。因此必須使用preventDefault();
一個控制的元件,每個state都關聯一個處理函式,因此可以修改或驗證使用者輸入。例如,讓輸入的名字強制轉化為大寫字母。
Textarea tag
In HTML, a <textarea>
element defines its text by its children.一般是firstChild。因為在HTML DOM中,text,attribute都是節點。
而在React中,text則被當做a value attribute處理。在constructor可以初始化這個value。
Select Tag
IN HTML
而在React中,則constructor初始化這個預設選項。this.state = {value: 'coconut'};
⚠️ :select接受array傳入value屬性 <select multiple={true} value={['B', 'C']}>
一句話,<input type="text">. <textarea> <select>都接受a value attribute.
練習程式碼:https://codepen.io/gaearon/pen/JbbEzX?editors=0010
- 一類元素對應一個觸發的方法。最好不要寫一起,不易讀。如select,和textarea的onChage={}應該分別寫不同的方法。偏要寫一起的話,使用name進行區分。
- preventDefault();必須得有,否則回到預設頁面。
- 不要瞎嘗試,看現成的案例,或找相關文件。下午耽誤了不到一個小時,就是因為在select和textarea元素的屬性上不清楚,竟然想自定義屬性?!!
Lifting State up :
把狀態放到作為祖先的元件中,子元件則可以共享這個state了。 這麼做的目的:多個元件共用一個相同的變化的資料。即:lifting the shared state up to their closest common ancestor。
首先, 把TemperatureInput中的state移動到Calculator中。讓它成為兩個TemperatureInput例項的source of truth 。如此input會同步變化。
第一,把this.state.temperature 替換為this.props.temperature。props來自Calculator。
因為props是隻讀的,同時來自父類,所以TemperatureInput不能直接控制它。
解決辦法是讓元件可控,即讓TemperatureInput從父元件接收除了溫度props外還有溫 度的控制方法onTemperatureChange props。
第二,在render()中,const temperature = this.props.temperature;
然後,設計Calculator元件。
state為temperature和 scale單位符號。這就是lifted up的state。它作為source of true 服務於所有子元件。
本例中,只要儲存一個格式的資料即可,另一個格式隨時通過換算來顯示結果。
增加處理Celsius和Fahrenheit的方法,用於設定setState.
在render(){}中進行celsius和fahrenheit的資料計算。
在return()中,呼叫TempertureInput元件兩次,分別傳入state,和控制方法。
總結: recap
- React calls the function specified as onChange on the DOM <input>。本例就是在溫度輸入元件TemperatureInput中的handleChange() 方法.
- handleChange方法使用this.props.onTemperatureChange(),props來自父元件Calculator。
- 父元件指定了傳入的props中的方法是F還是C。傳入哪個方法依據我們輸入input的位置。
- 在這些方法中,父元件因為呼叫this.setState()更新state,所以會再渲染。
- React呼叫父元件的render方法內部,兩種格式的溫度會再計算。
- 然後渲染方法會return 2個不同scale,temperature及其控制方法的TemperatureInput元件。
- React Dom更新DOM來匹配輸入的值。我們剛才輸入值的input框得到它當前的值,另以一個input框更新轉換的溫度值。
每個元件都是獨立的,都可以繼承一個父元件的完整state。
parseFloat(string)方法,把string-> 浮點數。
parseInt(string) 轉化為整數。
Number.isNaN(),當算數運算返回一個為定義的或無法表示的值時,NaN就產生了。
React Developer Tools用於檢查資料有chrome, Safari等主流版本。
codepen中使用他人的程式碼,先fork再選擇Change View > Debug。就可以使用了。
const output = convert(input); convert是傳進來的函式。
const rounded = Math.round(output *1000)/1000?
Math.round(x),返回給定數字的值四捨五入到最接近的整數。乘以1000是為了保留小數。
Composition vs Inheritance
React有強大的組成模組,因此建議程式設計師多用Composition而不是繼承。類似Ruby min-in model 。
在Facebook中使用了上千的components,沒有用到元件繼承。
Props and composition可以靈活的客製化一個元件的外觀和行為。 因為元件可以接收各種各樣的props,包括原始的value, React element, function.
Containment:
一些元件事先不知道它們的children。因此可以使用特殊的props: {props.children }去傳遞children elements 然後直接進入它們的輸出。
任何在<FancyBorder>JSX tag內的元素, 作為props.children被傳遞給FancyBorder元件。
也可以客製化傳入,如下例子:
然後在SpliPane元件中插入{props.left}和{props.right}
全程式碼:
https://codepen.io/chentianwei411/pen/QrbXeq?editors=0010
Specialization
有時把某個元件當成特殊的某個元件。Ruby, 動物類包含有哺乳類,鳥類。
https://codepen.io/gaearon/pen/kkEaOZ?editors=0010
類似於繼承。元件FancyBorder繼承了Dialog中的屬性title,message
composition 也可以在class定義的元件中工作得很好。
https://codepen.io/gaearon/pen/gwZbYa?editors=0010
在ES6中,${}用來代替引號拼接。類似Ruby中的插入#{}。
alert("welcome," + this.state.login );
alert(`welcome, + ${this.state.login}`) ⚠️ 是`, 單引號和雙引號不行。
例項:用React設計一個小的功能:
在React中有兩類model data:props and state:
我的理解:props是元件之間資料,格式,函式等任何資料的傳遞,state是一個元件的定義的屬性。 可以用props傳遞state。
Step1:Break the UI into Component 層
首先是分析需求,用畫圖來幫助思考的行為。給每個元件起名字要適合它們的特點。
根據單一責任原理,一個元件理論上只做一件事情。隨著元件的成長,應該把它拆分為更小的子元件。
FilterableProductTable
SearchBar
ProductTable
ProductCategoryRow
ProductRow
先把架子搭起來。整體的層次hi.er.archy。
最簡潔的方法:先建立一個包括data model和render UI但沒有互動行為的版本。
因為建立一個靜態版本只需大量的type不需要思考,而互動設計則需要大量思考而type較少。
⚠️ state是為互動行為服務的,建立靜態版本無需使用state.
為了建立一個靜態版本的app,需要渲染資料模組,renders your data model。
應當建立那種可以反覆使用其他的元件和使用props傳遞data的元件。
props是把資料從父傳給子的一個辦法。
選擇建設元件的方式:
小的app:設計原則是從top->down, 大的複雜的app則相反bottom-up。
最後,用reusable的元件渲染你的資料model。作為靜態版本,元件中只有render()方法。
FilterableProductTable元件處於the top of the hierarchy, 它用props傳遞data model。
forEach方法
mySet.forEach(function callback(value1, value2, Set){
//your iterator
}[, thisArg])
callback: Function to execute for each element
value1,value2: The value contained in the current position in the Set.
當set物件no key時,value2也是值。這樣Array也可以使用forEach方法。
set: The set object that's being traversed.
thisArg: Value to use as this when executing callback
對集合中的每個原素執行提供的callback函式一次,它不返回任何值。
用到了關鍵字key.
Step3: Identify the minimal(but complete)Representation of UI State 識別最小的UI state
為了正確建立app,首先,思考哪些是最小變化的state。這裡也提出dry 原則。
找出絕對的代表最小state。
本例:
- 原始的產品列表
- 使用者輸入的搜尋文字
- checkbook的值
- 產品檢索後的列表
思考:找出state。問3個問題:
- Is it passed in from a parent via props? If so, it isn't state. (存在於層次較高的元件)
- Does it remain unchanged over time? If so, it isn't state. (值肯定是會改變)
- Can you compute it based on any other state or props in you component? If so, it isn't state.(不會參與其他state/props的計算)
最後,我的的state是:
The search text that user has entered, the value of the checkbox
Step4: Identify where your state should live
識別這些state存在於哪個元件。
記住:React是關於單向資料流動的元件層級關係。對應新手來說,不容易理解元件應該擁有什麼state。所以跟隨以下思考步驟:
- 識別每個元件基於state渲染了什麼。
- 找到一個共同的擁有者元件,在層級上比其他元件更高的元件需要state。
- 在層級上更高的元件應當擁有state.
- 如果不能找到一個元件讓它適合擁有一個state, 則建立一個新的元件來持有這個state並且這個新元件在層級上應該高於這個普通元件。
- ProductTable 擁有檢索產品列表(基於state和SearchBar)
- The common owner 元件 是 FilterableProductTable
- 檢索文體和checked 值在FilterableProductTable中是可以的
首先:增加一個例項特性instance property this.state={filterText: '', inStockOnly: false}
其次:傳遞filterText和inStockOnly給ProductTable 和 SearchBar作為一個prop.
最後:使用這些props來檢索在ProductTable中的Rows
indexOf(searchElement[, fromIndex]) :
returns the first index at which a given element can be found in the string/array, or -1 if it is not present.
indexOf()方法返回呼叫String/Array物件中第一次出現的指定值的索引,如果沒有找到則返回-1.⚠️indexOf是用嚴格模式,區分大小寫字母。如:
"Blue Whale".indexOf("blue") //returns -1
console.log(beasts.indexOf('bison', 2)); 輸出4 ,fromIndex開始搜尋的位置,如果這個索引大於等於陣列的長度,返回-1
var str = "To be, or not to be, that is the question.";
var count = 0;
var pos = str.indexOf('e'); // 相當於Ruby中的 index方法,Ruby沒有找到返回nil。
while (pos !==-1 ) {
count++;
pos = str.indexOf('e', pos + 1);
}
console.log(count); //display 4
Step5: Add Inverse Data Flow
第4步,當函式的props和state順著元件的層級hierarchy向下流動時,app正確地渲染。
現在,到了支援資料逆向流動的步驟。表格元件需要更新FilterableProductTable中的state。
當用戶改變form時,我們根據使用者的輸入input更新state。元件只能更新自己的state。所以FilterableProductTable會把更新的回撥行為傳給SearchBar。我們使用onChange事件。這個由FilterableProductTable傳遞的回撥行為叫做setState. 之後app就會更新state了,之後則是因為state的改變,導致state data沿著元件的層級順流而下,app渲染各個元件。
看起來複雜,但程式碼只有幾行。並且這種清晰的資料流動遍及整個app。
❌:上層元件傳遞的對state操作的方法的name,不能和傳入的元件中宣告的方法同名,這樣會造成控制組件中的某個輸入功能無效。本例子:
傳入的名字:handleInStock,元件中自身也在constructor中命名一個handleInStock自然會無效。
⚠️ :return關鍵字在Ruby和JavaScript中的理解。
Ruby中return可以用於結束當前迴圈並返回值 (當然還有別的地方也用return),而在JS中return用於結束函式執行並返回一個指定值,箭頭函式也是函式。
記住:程式碼是用來讀的而不是寫的。就是可讀行,可理解行強。清楚的模組很容易讀和理解。
當建立大的app的元件時,每個功能都用獨立的元件,因為會反覆使用元件,所以整體上程式碼會shrink收縮。