React Conf 2017在加利福尼亞州的聖克拉拉萬豪酒店圓滿落幕,這已經是Facebook舉辦的第三屆React官方大會了。
雖然不能參會,但是作為前端開發者,我們當然不能錯過這個絕佳的學習契機。
筆者利用清明假期,參看了YouTube上關於這次大會的記錄。一共34個精彩演講,對應34個視訊。獲益匪淺。
這裡,將會作為一個系列,對其中的幾篇演講進行翻譯和分析。並輔助以code demo,幫助大家理解。
歡迎關注我的簡書或掘金賬號,也歡迎在Github上follow,最新的大會code demo,便可第一時間掌握。
今天為大家介紹的是Ben Ilegbodu的主題:React + ES next = ♥
Ben Ilegbodu是“為數不多的”參會有色程式設計師,黑人程式設計師如同女性程式設計師一樣鳳毛麟角。但是,本次分享主題很有愛,很有營養,精彩程度絲毫不打折扣。如果你不瞭解React也不要緊,因為這次講的是ES6、ES7在React中的應用。所以,其實是對下一代ES的普及和介紹。
本文將以
- Destructuring、
- Spread Opetator、
- Arrow Function、
- Promises、
- Async Functions
這幾方面展開。並通過nodeJS實現一個兼具前端和後端的小型“評論/留言 釋出閱讀系統”。
建議看這篇文章的同時,結合視訊一起研究:Ben Ilegbodu - React + ES next = ♥ - React Conf 2017
實現預覽
如圖,我們實現瞭如下的頁面。這是一個前端+後端的全棧小專案。
當然,樣式是極其簡陋的。作為“粗糙”的程式設計師,實在懶得在頁面上花時間。
我們可以在輸入框內輸入姓名和留言內容。並點選按鈕提交。後臺使用nodeJS express框架,實現對檔案的讀寫更新。
app.post('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
var comments = JSON.parse(data);
var newComment = {
id: Date.now(),
author: req.body.author,
text: req.body.text,
};
comments.push(newComment);
fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
if (err) {
console.error(err);
process.exit(1);
}
res.json(comments);
});
});
});複製程式碼
這是nodeJS的後端程式碼,如果不理解也沒關係。
接下來我們繼續回到演講。
解構Destructuring
在我們的專案中,最初版存在這樣的一段程式碼:
_handleCommentSubmit(comment) {
let comments = this.state.comments;
...
// remaining code
}複製程式碼
請務必記住這個_handleCommentSubmit函式,接下來的全文都是圍繞他展開,並進行一步步擴充。
這個函式的邏輯是對新提交的comment進行處理,首先他需要從state中讀取現有的comments陣列。
在使用解構賦值的情況下,我們重構為:
_handleCommentSubmit(comment) {
let {comments} = this.state;
...
// remaining code
}複製程式碼
也許這還看不出來解構到底有什麼作用,但是在邏輯多的時候,他是很有必要的。比如:
let author = this.state.author;
let text = this.state.text;複製程式碼
就可以寫為:
let {author, text} = this.state;複製程式碼
如果需要改變變數名時,就可以從:
let authorName = this.state.author;
let fullText = this.state.text;複製程式碼
改為:
let {author: authorName, text: fullText} = this.state;複製程式碼
再舉一個例子,在function component(React無狀態元件編寫的一種推薦形式)情況下:
function MyComponent(props) {
return (
<div style={props.style}>{props.children}</div>
)
}
<MyComponent style="dark">Stateless function!</MyComponent>複製程式碼
我們可以改寫為:
function MyComponent({children, style}) {
return (
<div style={style}>{children}</div>
)
}
<MyComponent style="dark">Stateless function!</MyComponent>複製程式碼
展開符Spread Opetator
還記得上面那個_handleCommentSubmit函式嗎?
接下來我們要進行擴充。首先我們將新提交的comment(即函式引數),新增一個時間戳作為id。接下來,
我們拿到舊的state.comments之後,就要將新的comment(包含id)加入到state.comments當中。
初版做法是:
_handleCommentSubmit(comment) {
let {comments} = this.state;
let newComment = comment;
newComment.id = Date.now();
let newComments = comments.concat([newComment]);
...
// setState + ajax stuffs
}複製程式碼
在使用展開符後,我們可以重構為:
_handleCommentSubmit(comment) {
let {comments} = this.state;
let newComment = {...comment, id: Date.now()};
let newComments = [...comments, newComment];
...
// setState + ajax stuffs
}複製程式碼
當然,展開符還有很多其他benefits;比如,平時我們可以使用Math.max方法對陣列求出最大值:
var arrayOfValue = [33, 2, 9];
var maxValueFromArray = Math.max.apply(null, arrayOfValue)複製程式碼
這個思路利用了apply接受一個陣列作為函式引數的特性。
使用展開符,我們就可以:
var arrayOfValue = [33, 2, 9];
var maxValueFromArray = Math.max(...arrayOfValue);複製程式碼
同理,我們可以這樣擴充一個陣列:
let values = [2, 3, 4];
let verbose = [1, ...values, 5];複製程式碼
當然,以上都是對陣列的展開。
對物件屬性的展開符的使用,也已經到了Stage3階段。今後,我們可以這樣寫程式碼:
let warriors = {Steph: 95, Klay: 82, Draymond: 79};
let newWarriors = {
...warriors,
Kevin: 97
}複製程式碼
而不必再使用Object.assign進行物件的擴充套件。
箭頭函式Arrow Function
箭頭函式帶來的好處無疑是對this的繫結。
現在再回到我們的_handleCommentSubmit函式。做完了初始化工作,我們需要將新的comment通過ajax,非同步傳送給後端。在成功的回撥函式中,進行setState處理:
$.ajax({
url: this.props.url,
data: comment,
success: function(resJson) {
this.setState({comments: resJson})
}.bind(this)
})複製程式碼
有經驗的同學注意到了我使用bind更改this指向的處理。
在ES6箭頭函式下,我們可以直接:
$.ajax({
url: this.props.url,
data: comment,
success: (resJson) => {
this.setState({comments: resJson})
}
})複製程式碼
我們再展開看看箭頭函式的幾種用法(使用方式):
- 匿名函式,單引數情況,比如實現一個陣列的各項平方:
let squares = [1, 2, 3].map(value => value * value);複製程式碼
- 匿名函式,多引數情況,比如實現一個陣列的各項累加:
let sum = [9, 8, 7].reduce((prev, value) => prev + value, 0)複製程式碼
這時候,需要把引數放在括號之中。
- 非匿名函式,無返回值情況:
const alertUser = (message) => {
alert(message)
}複製程式碼
這時候,函式體用花括號圍起來。
- 更復雜的情況,比如上面我們用到過的:
const MyComponent = ({children, style}) => (
<div style={style}>{children}</div>
)複製程式碼
Promises
上面的程式碼我們使用了jquery的ajax方法傳送非同步請求,這可能在某些情況下出現“回撥地獄”的情況。為此,我們使用基於Promise的fetch方法,進行重構:
_handleCommentSubmit(comment) {
// ...
fetch(this.props.url, {
method: 'POST',
body: Json.stringify(comment)
})
.then((res)=>res.json())
.then((resJson)=>{
this.setState({comments: resJson})
})
.catch((ex)=>{
console.error(this.props.url, ex)
})
}複製程式碼
當然,我們可以把上述程式碼做的更抽象:
const fetchJson = (path, options) => {
fetch(`${DOMAIN}${path}`, options)
.then((res)=>res.json())
}複製程式碼
我們在拉取評論時,就可以:
const fetchComments = () => fetchJson('api/comments')複製程式碼
基於promises的fetch,讓非同步請求變的靈活可靠。
同時,我再安利一個基於promises的sleep函式:
const sleep = (delay = 0) => {
new Promise((resolve)=>{
setTimeout(resolve, delay)
})
}
sleep(3000)
.then(()=>getUniqueCommentAuthors())
.then((uniqueAuthors)=>{this.state({uniqueAuthors})})複製程式碼
回到我們的Project中,在後端nodeJS實現裡,我們把所有的評論存在data/comments.json檔案當中。
在渲染檢視時,就不可避免地要進行對data/comments.json檔案讀取,這個過程也應該是非同步完成的:
const readFile = (filePath) => (
new Promise((resolve, reject)=>{
fs.readFile(filePath, (err, data)=>{
if (err) {reject(err)}
resolve(data)
})
})
)
readFile('data/comments.json')
.then((data)=>console.log('Here is the data', data))
.catch((ex)=>console.log('Arg!', ex))複製程式碼
Async Functions
當然,Promises實現也有缺點。
更先進的方式,就是使用Async,回到我們的_handleCommentSubmit方法,我們可以重構為:
async _handleCommentSubmit(comment) {
try {
let res = await fetch(this.props.url, {
method: 'POST',
body: JSON.stringify(comment)
});
newComments = await res.json();
}
catch (ex) {
console.error(this.props.url, ex);
newComments = comments;
}
this.setState({comments: newComments});
}複製程式碼
總結
這篇演講生動形象地闡釋了React是怎樣與ES next融合天衣無縫的。不論是React也好,還是ES也好,其實筆者認為說到底,都是為了更大限度地解放生產力。
歡迎讀者與我交流,有任何問題可以留言。今後幾天,將有更多新鮮的react conf視訊翻譯奉獻給大家。
Happy Coding!
PS: 作者Github倉庫,歡迎通過程式碼各種形式交流。