必須說小編實在不喜歡React的撰寫方式跟風格,畢竟一開始吃了不少鱉。不過既然我學習過我還是簡易做個筆記吧。
Ps.本篇網誌是有段時間前寫的。
有人會問既然小編不喜歡React,但為什麼還是要學呢?一切都是因為……
網路上工作職缺打開,每個都要React & Angular開發經驗者佳,WTF!! 所以今天你只需要知道,你學會了 = More Money!! 如何~有沒有動力了~
一言以蔽之就是:這是一個前端JS框架。OK~這句話似乎又衍生了更多問題,用在什麼地方?好不好用?優點是什麼?跟Angular比較起來差別是?為什麼我要用?
OK,為什麼我要用?大部分情況下,我們都會有專案期限的壓力,通常在這種壓力下,一般人(小編就是一般人啦)就會選用自己最擅長,最有把握的語言(工具)去完成專案,這有什麼優缺點呢? 優點就是專案如期完成的機率大幅提升。缺點呢? 井底之蛙,你會的永遠就是這些,你的速度永遠這麼慢,且你永遠不知道世界有多大,進步有多快。保哥曾在篇網誌上說過,一個工程師的價值在於它是否願意花時間去縮短他的coding時間。簡單說就是提升工作效率啦。一個網站從到尾自己刻的時代已經過去了,什麼都就講究自動化的今天,資料要越乾淨越好,工程師做的事情要越少越好,所以這些前端框架就應運而生。
比起Angular又如何呢?先說小弟我其實是支持Angular啦,特別是A2已經出到RC版的今天,差不多可以進場了,後面再講資料流的時候我會跟大家提到,不過有時專案需要,我們還是要搞懂React這傢伙在搞什麼。
接下來開始進入React的核心
React並不能算是個完整個框架,官方推出React的時候希望落實關注點分離,讓大家可以專注於View的開發,所以充其量React只是MVC中的View而已(有一點點的M啦),其最重要也是最強大的功能就是Render:宣染 VDOM(虛擬DOM),我敢保證,你看完說明肯定還是不懂我在說什麼,沒關係就直接進我們第一個範例吧
第一支React
1
2
3
4
5
6
7
8
9
10
11
|
HTML:
<div
id="example"></div>
JSX:
ReactDOM.render(
<h1>Hello,
world!</h1>, //將這個物件,丟到下面這個元件之中
document.getElementById('example') //丟到我裡面吧
);
render result:
<div
id="example"><h1>Hello, world!</h1></div>
|
把這些寫在JS代碼中的HTML(VDOM)宣染到畫面上,這就是React最大的賣點,集中撰寫,跟宣染速度快,因為它會透過底層演算,用最小的幅度去修改畫面(不過最近被Vue跟Angular2打臉了),附上一篇文章
對React來說,很重要的一個觀念,你畫面上看到的所有東西都是元件(components)
Component props
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var HelloMessage = React.createClass({
render:
function() {
return <h1>Hello
{this.props.name}</h1>; => 所以這邊會輸出Hello John
}
});
ReactDOM.render(
<HelloMessage
name="John" />, =>設定這物件的name屬性為John
document.getElementById('example')
);
開嗆時間:
angular 這樣寫就好了 <h1
ng-init="name='John'">Hello {{name}}</h1>
|
所以其中一個React很大的優勢就是,可以局部重購,因為可以把畫面拆成許多Components,元件中也可以包元件
在JSX中{}內可以正常的寫JS,若是放入array,也會批次被render出來
Array for React
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
var names =
['Alice', 'Emily', 'Kate'];
ReactDOM.render(
<div>
{
names.map(function
(name) {
return <div>Hello,
{name}!</div>
})
}
</div>,
document.getElementById('example')
);
Demo
開嗆時間:
angular 這樣寫就好了 <div ng-repeat="name in
names">{{name}}</div>
|
Children
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
var Comment =
React.createClass({
render:
function() {
return (
< div className = "comment" >
<
h2 className = "commentAuthor" >
{ this.props.author}
<
/h2>
{this.props.children}
< /div>
);
}
});
var CommentList = React.createClass({
render:
function() {
return (
<
div className = "commentList" >
<
Comment author = "Pete Hunt" > This is one comment
< /Comment>
<
Comment author = "Jordan Walke" > This is * another *
comment < /Comment >
<
/div>
);
}
});
ReactDOM.render(
<CommentList
/>,
document.getElementById('container')
);
/*
這裡可以用children取子節點,如果子節點是很多個物件,那取回來就會是陣列,例如這樣
</Parent>
<ChildrenA
/>
<ChildrenB
/>
<ChildrenC
/>
</Parent>
*/
|
attr 共用屬性
1
2
3
4
5
6
7
8
9
10
11
|
var attr = {}; => 共用的屬性,不想重複打可以這樣做
attr['id'] = "a";
attr['name'] = "b";
attr['class'] = "c";
render(
<input
{...attr} onChange={this.valueChg.bind(this,tr_index,td_index)}/>
) //event如果要綁定參數,必須用bind(this......)的方式呼叫
render result:
<input id="a" name="b" class="c" onChange={this.valueChg.bind(this,tr_index,td_index)}/>
|
event, refs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
var MyComponent =
React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input
type="text" ref="myTextInput"/> // reference參考,在這可以把它當成向指定idㄧ樣指定物件
<input
type="button" value="Focus the text input" onClick={this.handleClick}
/> //onClick觸發handleClick()
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
|
state
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
var LikeButton =
React.createClass({
getInitialState:
function() { =>初始化state
return {liked: false};
=> liked = false;
},
handleClick:
function(event) {
this.setState({liked:
!this.state.liked}); => 設定liked 反向
},
render: function() {
var text
= this.state.liked ? 'like' : 'haven\'t liked';
return (
<p
onClick={this.handleClick}>
You
{text} this. Click to toggle.
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
|
React裡面有個很重要的觀念,state改變,元件就會重新render,所以要想改變畫面上的東西,只有model改變是不夠的,看看以下範例
rerender
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
var Input = React.createClass({
getInitialState:
function() { // 設定初始state
return {value: 'Hello!'};
},
handleChange:
function(event) {
this.setState({value:
event.target.value}); // 若不藉此改變state畫面上的數值是不會更動的,除非是改用defaultValue,但如此便無法掌握資料了
},
render:
function () {
var
value = this.state.value;
return (
<div>
<input
type="text" value={value} onChange={this.handleChange} />
<p>{value}</p>
</div>
);
}
});
ReactDOM.render(<Input/>,
document.getElementById('example'));
開嗆時間:
Angular只要這樣寫
<input type="text" ng-model={value}
/>
<p>{{value}}</p>
|
this.props通常是使用在父元素傳值給子元素的情境上this.props 以及 this.state。這兩個屬性有不同的特性與行為,在使用上,官方建議靜態資料使用this.props,動態資料使用this.state。
value
defaultValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
var MyComponent = React.createClass({
getInitialState:
function() {
return {a: "a"};
},
handleClick1:
function() {
this.refs.myTextInput1.value="b";
this.refs.myTextInput2.value="b";
},
handleClick2:
function() {
this.setState({a:"b"});
},
change1:
function(e) {
alert(e.target.value)
},
change2:
function(e) {
alert(e.target.value)
},
render: function()
{
return (
<div>
<input
type="text" ref="myTextInput1" defaultValue={this.state.a}
onChange={this.change1}/>
//直沒有被綁定,可以透過onChange偵聽,也可以透過事件改變,但資料不好掌握
<br/> //
因為必須是完整的巢狀式迴圈,所以一定要加上反斜線
<input
type="text" ref="myTextInput2" value={this.state.a}
onChange={this.change2}/>
//值被state.a綁定了,也可以透過onChange偵聽,但值不會更改,但可以被事件改變(不觸發onChange),但這樣它的優勢(資料好掌握)就沒了,一旦資料被rerender,值會再回到state.a
<br/>
<input
type="button" value="changeA" onClick={this.handleClick1}
/>
<br/>
<input
type="button" value="changeState" onClick={this.handleClick2}
/>
</div>
);
}
});
ReactDOM.render(
<MyComponent
/>,
document.getElementById('container')
);
|
className
1
2
3
4
5
6
7
8
9
10
11
|
var HelloMessage =
React.createClass({
render:
function() {
var
classString = "msg";
return <div
className={classString}>Hello, I'll be there</div>; => 其實就是正常html的class,但class這名詞是React的專有名詞,所以不能直接使用
}
});
ReactDOM.render(
<HelloMessage
/>,
document.getElementById('example')
);
|
其實React也可以做到two-way-data-binding
two-way-data-binding
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
var WithLink =
React.createClass({
/* 載入
React.addons.LinkedStateMixin 中的方法 (this.linkState)*/
mixins:
[React.addons.LinkedStateMixin], //應該是類似擴充方法
getInitialState:
function () {
return {message: 'Hello!'}
},
render:
function () {
/*邏輯處理的地方*/
return (
<div>
<input
type='text' valueLink={this.linkState('message')} />
{this.state.message}
</div>
)
}
});
|
但是two-way-data-binding做完可以在render裡面做些簡單的視覺邏輯,但render裡面本來就不適合放資料處理邏輯,所以如果真的要走資料流,也不適合這種方式,這也是這個好用的功能卻一值沒沒無聞的原因。
大致上,元件執行生命週期方法的情形可分為四種
初始化,第一次 render
getDefaultProps()
getInitialState()
componentWillMount()
render()
componentDidMount()
props 發生改變時
componentWillReceiveProps(nextProps)
shouldComponentUpdate(nextProps,
nextState)
componentWillUpdate(nextProps,
nextState)
render() => 我試不出來
componentDidUpdate(prevProps,
prevState)
state 發生改變時
shouldComponentUpdate(nextProps,
nextState)
componentWillUpdate(nextProps,
nextState)
render()
componentDidUpdate(prevProps,
prevState)
元件 unmount 卸載時
componentWillUnmount()
來看個簡單範例
生命週期範例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
var Hello = React.createClass({
getInitialState:
function () {
return {
opacity: 1.0
};
},
componentDidMount:
function () { => 必須在宣染完後才能開始動作
this.timer
= setInterval(function () {
var
opacity = this.state.opacity;
opacity
-= .05;
if (opacity
< 0.1) {
opacity
= 1.0;
}
this.setState({
opacity:
opacity => 變更透明度後重新寫入state,react會自動重新宣染該物件
});
}.bind(this), 100);
=> bind(this)在JSX裡面會常常用到,但這不是JSX特有的寫法,而是JS本身的特性,告訴在這個func裡面的this是誰
},
render:
function () {
return (
<div
style={{opacity: this.state.opacity}}>
Hello
{this.props.name}
</div>
);
}
});
ReactDOM.render(
<Hello
name="world"/>,
document.getElementById('example')
);
|
Ajax
react本身是無法做到這件事的,必須透過其他框架,例如jQuery。React還有個很大的特色是,跟各家框架都幾乎完美相容,低耦合,甚至它已可以做到類似lazyLoad的效果
var RepoList = React.createClass({
getInitialState:
function() {
return {
loading: true,
error: null,
data: null
};
},
componentDidMount() {
this.props.promise.then(
=> 等待ajax取資料回來
value
=> this.setState({loading: false, data: value}),
error
=> this.setState({loading: false, error: error}));
},
render: function() {
if (this.state.loading)
{// 開始資料分析
return <span>Loading...</span>;//資料Loading中
}
else if (this.state.error
!== null) {
return <span>Error:
{this.state.error.message}</span>;
}
else {
var
repos = this.state.data.items;
var
repoList = repos.map(function (repo, index) { // map在寫react中也常用到,作用類似forEach把陣列資料處裡回傳(angular 就是 ng-repeat)
return (
<li
key={index}><a href={repo.html_url}>{repo.name}</a>
({repo.stargazers_count} stars) <br/> {repo.description}</li>
);
});
return (
<main>
<h1>Most
Popular JavaScript Projects in Github</h1>
<ol>{repoList}</ol>
</main>
);
}
}
});
ReactDOM.render(
<RepoList
promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
/>,//$.getJSON是jQuery的方法
document.getElementById('example')
);
|
所以網路上許多人也會用React+backbone讓它變成一個完整的MVC框架,讓它開發起來更加靈活
甚至,React還可以寫在server端,當然前提是你要用同樣是JS開發的NodeJS伺服器。這樣可以在server端直接render出畫面,並且在前端繼續開始react的生命週期。
不過感覺有點像php用echo 輸出html 或 .net用lazor輸出html一樣,前後端就又絞在一起了,成為所謂的全端程式,小編是不太喜歡這樣啦
恩.....下面就nodejs跟react混用,沒啥好介紹的。
React in server
var http = require('http'),
browserify =
require('browserify'),
literalify =
require('literalify'),
React =
require('react'),
ReactDOMServer
= require('react-dom/server');
var App = require('./app');
http.createServer(function(req, res) {
if (req.url
== '/') {
res.setHeader('Content-Type', 'text/html');
var props = {
items:
[
'Item
0',
'Item
1'
]
};
var html = ReactDOMServer.renderToStaticMarkup(
<body>
<div
id="content" dangerouslySetInnerHTML={{__html:
ReactDOMServer.renderToString(<App
items={props.items}/>)
}}
/>
<script
dangerouslySetInnerHTML={{__html:
'var
APP_PROPS = ' + JSON.stringify(props) + ';'
}}/>
<script
src="//fb.me/react-0.14.0.min.js"/>
<script
src="//fb.me/react-dom-0.14.0.min.js"/>
<script
src="/bundle.js"/>
</body>
);
res.end(html);
} else if (req.url
== '/bundle.js') {
res.setHeader('Content-Type', 'text/javascript');
browserify()
.add('./browser.js')
.transform(literalify.configure({
'react': 'window.React',
'react-dom': 'window.ReactDOM',
}))
.bundle()
.pipe(res);
} else {
res.statusCode
= 404;
res.end();
}
}).listen(3000, function(err) {
if (err) throw err;
console.log('Listening
on 3000...');
})
|
學到這裡已經可以開始開發小型的React專案了,還可以有簡單的資料流。
=================================================
我是分隔線,休息一下,喝口水 =========================================================
WTF,簡單資料流??
你應該有發現他的資料流似乎都是父傳子,我想要兄弟相傳,親戚相傳,薪火相傳可以嗎?原則上是可以的,可以透過共同的祖先來資料源與資料處裡。
那問題來了,如果沒有共同的祖先呢?記得React的特性就是東一塊西一塊嗎?而且React當初設計本來就不是為了傳遞資料而設計,所以在資料傳遞上的寫法都會很複雜,所以如果有頻繁的資料傳遞,開發簡單的小程式你就頭大了(說好的React學習曲線平緩呢?),所以這時候官方就提出了一個設計模式Flux,看清楚我講的不是框架,不是語言,是設計模式!!
那這個Flux又是何方神聖呢?這個.......我們就留到後話再說吧!