React 是一個 View 框架,主要的用途有幾個:
- 將UI物件化
- 運用 props 來傳輸數據
- 依照 state 變化來調整 view
- 使用虛擬 DOM 提升效能
在開始之前,請預先安裝 webpack + babel + react依賴環境
接下來,會一步步來說明:
【React element 及 React Dom】
React DOM負責管理要輸出到指定目標的內容 例如,先產生一個div
<div id="root"></div>
接下來,建立一個react element內容, 在透過 ReactDOM.render() 輸出到 root
import React from 'react';
import ReactDOM from 'react-dom';
let react_element = <h1>Hello World!</h1>;
ReactDOM.render(react_element, document.getElementById('root'));
immutable
React element只要經過ReactDOM.render()輸出後,就具有**不可變動(immutable)**的特性, 因此輸出的主要架構(包括子元件及屬性)將維持固定, 如果需要更新內容,也只會額外處理避免操作整個DOM,盡可能做到最小幅度異動。
例如,官網提供的時間範例語法
import React from 'react';
import ReactDOM from 'react-dom';
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
實際執行,會發現每秒取得一次時間後,都只會變更指定的位置,而不是整個react element區塊
這是因為,ReactDOM 會比較前後差異,並且只針對有差異的地方進行最小幅度修改。
建立 Components 的三種方式及export用法
(1) function
Components 可以將UI分離出來,並且可以重複使用 功能跟JavaScript function一樣 Components可以傳入props參數,並且返回 React elements
function Title_elem(obj){
return <h1>{obj.title}</h1>;
}
let element = <Title_elem title="Hello World" />;
ReactDOM.render(element, document.getElementById('root'));
(2) 物件
也可以使用物件的方式建立
/*
./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
const A = {
a: function (props){
return <div>this is a</div>;
},
b: function (props){
return <div>this is b</div>;
},
c: function (props){
return <div>this is c</div>;
}
}
ReactDOM.render(
<div>
<A.a />
<A.b />
<A.c />
</div>, document.getElementById('root'));
(3) Class 方式 + Extends React.component
通常會使用 ES6 的 class 來管理 Components Class 只要擴充 React.Component ,就可以使用 React Component功能:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
export imoprt Component
myComonents.js
export myComponent;
App.js
import myCommponetn from './myComponent'
接下來,要介紹一下JSX
JSX
JSX比較接近於javascript而不是HTML,使用的是駝峰式(camelCase)命名方式。 JSX架構與HTML相當類似,因此,只要寫你熟悉的HTML就能開始建構JSX內容, 並且,透過Babel就能將JSX風格轉換成React語句, 這裡我們會先跳過 react 語句,直接以JSX為重點。
並且在rendering之前,ReactDOM 進行格式化,會將 JSX 裡的數據資料轉換為字符,因此,正常情況使用都不必擔心XSS攻擊的風險。
例如,在這案例的JSX {} 可以傳入外來的變數值,因此可能會有夾帶XXS攻擊的字串,
return <div>this is c {'<script>console.log("攻擊");</script>'}</div>;
這些內容會被判定為 HTML-unescaped ,而直接處理成字串格式
接下來,我們會一邊學習 react架構,一邊講解 JSX 用法:
基本架構
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(<h1>Hello World</h1>, document.getElementById('root'));
崁入變數
import React from 'react';
import ReactDOM from 'react-dom';
let name = 'Adam';
ReactDOM.render(<h1>Hello World! {name}</h1>, document.getElementById('root'));
崁入函式
import React from 'react';
import ReactDOM from 'react-dom';
let userfn = (user)=>{
if(user.age>=18){
return user.firstname+' '+user.lastname
}else{
return 'Your should not be here! age:'+user.age;
}
};
let user = {firstname: 'Adam', lastname: 'OuYang', age: 31};
ReactDOM.render(<h1>Hello {userfn(user)}~</h1>, document.getElementById('root'));
HTML屬性
屬性值
用變數賦予屬性值,且不必使用引號包裹
import React from 'react';
import ReactDOM from 'react-dom';
let imagesrc = "https://i.pinimg.com/originals/12/e5/cb/12e5cbbb63024460755560bb7e03f0a4.png";
ReactDOM.render(<img src={imagesrc}></img>, document.getElementById('root'));
class -> className
JSX使用駝峰式(camelCase)來描述原本的HTML屬性 在HTML標籤中,我們常會用 class 等屬性, JSX要修改成 className
ReactDOM.render(<h1 className="main_title">Hello</h1>, document.getElementById('root'));
for -> htmlFor
使用 htmlFor 來對應 for功能
import React from 'react';
import ReactDOM from 'react-dom';
function Hello(props){
return <div className='hello'>
<label htmlFor="a">
Hello
</label>
<input id='a' type='text' value='hi' />
</div>
}
ReactDOM.render(<Hello />, document.getElementById('root'));
Boolean 操作屬性
HTML標籤有些屬性可以用 Boolean 來操作 預設為 false
<input id='a' type='text' disabled={false} value='hi' />
搭配Components
function Img_elem(obj){
return <img src={obj.src} />;
}
const imagesrc = "https://i.pinimg.com/originals/12/e5/cb/12e5cbbb63024460755560bb7e03f0a4.png";
ReactDOM.render(<Img_elem src={imagesrc} />, document.getElementById('root'));
參考官網提供的範例
function formatDate(date) {
return date.toLocaleDateString();
}
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name} />
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
const comment = {
date: new Date(),
text: 'I hope you enjoy learning React!',
author: {
name: 'Hello Kitty',
avatarUrl: 'http://placekitten.com/g/64/64'
}
};
ReactDOM.render(
<Comment
date={comment.date}
text={comment.text}
author={comment.author} />,
document.getElementById('root')
);
ES6 Class
將 components 獨立出來, 這次我們透過ES6 class 建立一個 component 透過 default 可以將此類別認定為預設, 在這裡面,透過 constructor 來設定props,並且要透過 super()屬性才能連結回 React.Component,修改React.Component的constructor屬性。 內容則套用 constructor定義的 state /client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date().toLocaleTimeString()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date}.</h2>
</div>
);
}
}
這裡是 ./client/index.js
/*
./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<App />, document.getElementById('root'));
Component Mount and unMount
https://reactjs.org/docs/state-and-lifecycle.html React Component 提供元件加載及卸載功能
componentDidMount() - 當元件輸出至DOM之後,就會執行 componentWillUnmount - 當元件從DOM移除,就會執行 例如,這裡實作一個時間顯示器
範例: ./client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
關於 constructor setState()
上方案例中,會看到一個比較特殊的狀況 在 constructor 我們設定了 this.state = {date:初始值} 在計時器呼叫的函式要變更初始值時,要使用 this.setState({data: 變更值})
要留意的是,不能使用這個寫法,會造成無法 re-render
this.state.date = new Date();
而是透過this.setState()修改constructor 的state值,才能成功re-render
this.setState({
date: new Date()
});
使用 setState()的原因是為了效能, 因為React會將多處的setState()呼叫都統合起來,並一次更新
事件
https://reactjs.org/docs/handling-events.html 在說明事件相關範例時,都是使用 index.js 內容, 後續將專注在App.js 解說事件的用法 client/index.js
/* ./client/index.js*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
ReactDOM.render(<App />, document.getElementById('root'));
onClick
onClick 可以用來觸發點擊事件,在物件中, 如果要呼叫內部方法,要以 this.方法名稱 來呼叫 例如,範例中的 A() B() 方法,在render都必須要用 this.A, this.B 來呼叫
另外,如果呼叫方法之後,還需要繼續呼叫其他方法等callback行為, 則必須要在 constructor 裡面宣告this,讓render的元件可以callback 例如,在範例中,A()就有在 constructor 實作宣告this,因此可以繼續在A來回呼內部 Msg 方法
或者,可以在 render 使用 arrows 來達到 callback,但是這樣的作用可能會有re-rending的問題,因此還是建議用上面A的方法
如果是直接在render寫function,就不必再額外加 this 例如,範例中的函式 Fn,但Fn同樣無法callback
直接從範例中來了解上面內容:
client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.A = this.A.bind(this);
}
A(){
console.log('The link A was clicked.');
console.log(this);
this.Msg();
}
B(){
console.log('The link B was clicked.');
console.log(this);//undefined
this.Msg();//TypeError
}
Msg(){
console.log('Yes! I can callback');
}
render() {
function Fn(e) {
e.preventDefault();
console.log('The link C was clicked.');
console.log(this);//undefined
this.Msg();//TypeError
}
return (
<div>
<h1>Hello, world!</h1>
<a href="#" onClick={this.A}>Call A</a><br/>
<a href="#" onClick={this.B}>Call B</a><br/>
<a href="#" onClick={(e) => this.B(e)}>Call B</a>(This syntax ensures `this` is bound within B)<br/>
<a href="#" onClick={Fn}>Call Fn</a><br/>
</div>
);
}
}
refs
React 支援一個特殊的屬性 Refs,可以綁定到任何被 render的組件上面,確保能在任何時間都能取得最新的值或實作。 https://reactjs.org/docs/refs-and-the-dom.html
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myInput" />
<input
type="button"
value="點擊獲得焦點"
onClick={this.handleClick}
/>
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
傳入事件參數
這兩種方式,都可以用來傳入參數
<a href="#" onClick={this.A.bind(this,'1','2')}>Call A</a><br/>
<a href="#" onClick={(e) => this.A('1','2', e)}>Call A</a><br/>
將前面的例子修改成可以傳參數: client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
// This binding is necessary to make `this` work in the callback
this.A = this.A.bind(this);
}
A(id,id2,e){
console.log(e);
console.log('The link A was clicked. id is :'+id+', id is:'+id2);
console.log(this);
this.Msg();
}
B(id,id2, e){
console.log(e);
console.log('The link B was clicked. id is :'+id+', id is:'+id2);
console.log(this);
this.Msg();
}
Msg(){
console.log('Yes! I can callback');
}
render() {
function Fn(id, id2, e) {
console.log(e);
console.log('The link C was clicked. id is:'+id+', id is:'+id2);
// console.log(this);//undefined
// this.Msg();//TypeError
}
return (
<div>
<h1>Hello, world!</h1>
<a href="#" onClick={this.A.bind(this,'1','2')}>Call A</a><br/>
<a href="#" onClick={(e) => this.B('1','2', e)}>Call B</a>(This syntax ensures `this` is bound within B)<br/>
<a href="#" onClick={Fn.bind(this,'1','2')}>Call Fn</a><br/>
<a href="#" onClick={(e) => Fn('1','2', e)}>Call Fn</a><br/>
</div>
);
}
}
Components Rendering
Components displays Components 及事件參數
在React,可以封裝許多 Components 而 Components 也可以使用其他 Components 例如,這裡舉例一個依照使用者登入狀況,先透過 Greeting Components 決定要返回甚麼介面 當使用者登入,Greeting Components 會返回 UserGreeting Components,讓他被 rending 若未登入,則會返回 GuestGreeting Components
關於 展開運算子 Spread Operator …
在這例子中,我們還使用到了一個ES6 展開運算子(Spread Operator) ...
var datas = {name:'adam', city:'taipei', country:'taiwan'}
ReactDOM.render(
<div>
<A {...datas}/>
</div>
,document.getElementById('root')
);
function A(props){
return (
<span>
I'm {props.name}, I live in {props.city},{props.country}
</span>
);
}
CVT2HUGO: ,可以將 props 物件一次pass給Component。 同理,也可以在多個components之間,以spread attributes傳遞props
var datas = {name:'adam', city:'taipei', country:'taiwan'}
ReactDOM.render(
<div>
<A {...datas}/>
</div>
,document.getElementById('root')
);
function A(props){
return (
<span>
I'm {props.name}, <B {...props} />
</span>
);
}
function B(props){
return (
<span>I live in {props.city}, <C {...props} /></span>
);
}
function C(props){
return (
<span>{props.country}</span>
);
}
範例: client/index.js
/*
./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
function UserGreeting(props) {
return <h1>{props.user} Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props){
if(props.isLoggedIn){
//透過...將props參數完整傳輸到 UserGreeting 且返回 Components
return <UserGreeting {...props} />
}else{
//返回GuestGreeting Component
return <GuestGreeting />
}
}
ReactDOM.render(<Greeting isLoggedIn={true} user='Admin'/>, document.getElementById('root'));
在類別進行 Components displays Components
這裡,我們將上述的內容搬到App.js, 在這裡,建立一個登入按鈕及訊息,並且要能根據登入狀況來切換按鈕文字及訊息
client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}
handleLoginClick() {
this.setState({isLoggedIn: true});
}
handleLogoutClick() {
this.setState({isLoggedIn: false});
}
render() {
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
function LoginButton(props) {
//這裡的 onClick={props.onClick} 會呼叫 A處的 this.handleLogoutClick
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
//這裡的 onClick={props.onClick} 會呼叫 B處的 this.handleLogoutClick
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
const isLoggedIn = this.state.isLoggedIn;
let button = null;
if (isLoggedIn) {
//A處
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
//B處
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
&& Operator
在 component 設計中,可以加入判斷式來決定要 rendering 的內容, 這裡說明 && 用法, 當我們判斷是否有訊息,如果訊息長度大於1且(&&)包含 element,就返回此 element 當然,element已經寫好,所以會是 true 因此這個component是否返回element 就要取決於訊息長度是否大於1
client/index.js
/*
./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}
const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));
? true : false
在component透過 ```?
client/index.js
CVT2HUGO: true : false``` 來判斷要返回的 element寫法
/*
./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 ?(
<h2>
You have {unreadMessages.length} unread messages.
</h2>
) : (
<h2>
You have no messages.
</h2>
)
}
</div>
);
}
//const messages = ['React', 'Re: React', 'Re:Re: React'];
const messages = [];
ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));
資料列 Lists 與 keys
在React資料列的處理有點特別 當我們輸出重複相同的姊妹群(Siblings)list時,React會提出警告,
Warning: Each child in an array or iterator should have a unique "key" prop.
keys
意思是,必須賦予該map渲染的每一個Siblings(姊妹群)元素唯一keys,讓React再生成元素之後,可以辨識誰是誰, 之後就能辨識出元素是否有新增、刪除、變更
這裡,據一個例子,當我們透過 map 迴圈生成Siblings元素,就要附上 key 這個相關的 li 群裡,此key 必須要有唯一性,但不必具有絕對唯一性。這點我們看完範例後,接續會再討論
這裡的key我們用的是陣列的索引值 client/index.js
/*
./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
function Hello (props) {
const numbers = props.num;
const NumList = numbers.map((v,k)=><li key={k.toString()}>{v}</li>)
const NumList2 = numbers.map((v,k)=><li key={k.toString()}>{v}</li>)
return (
<div>
<h1>Hello!</h1>
<ul>{NumList}</ul>
<ul>{NumList2}</ul>
</div>
);
}
const numbers = [1, 1, 2, 3, 5];
ReactDOM.render(<Hello num={numbers}/>, document.getElementById('root'));
keys 只需在該Siblings具有唯一性
剛剛提過,這個相關的 map 產出的Siblings(姊妹群)元素裡,此key 必須要有唯一性,但不必具有絕對唯一性。 這組key只是React方便於辨識該Siblings元素,所以,其他地方仍可以使用 例如,我們將陣列分別儲存在 numbers,numbers2,並且各自將map結果存在NumList Siblings, NumList2 Siblings 雖然 NumList, NumList2 的 key 有重複,但是不會影響
function Hello (props) {
const numbers = props.num;
const numbers2 = props.num;
const NumList = numbers.map((v,k)=><li key={k.toString()}>{v}</li>)
const NumList2 = numbers2.map((v,k)=><li key={k.toString()}>{v}</li>)
return (
<div>
<h1>Hello!</h1>
<ul>{NumList}</ul>
<ul>{NumList2}</ul>
</div>
);
}
表單控制
在 React,JSX事件、屬性都要遵循駝峰式寫法, 因此,表單送出事件,輸入欄位變更事件等…都要調整寫法
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
event.target
React 在 rendering 元素之後,如果事件要傳回元素本身,就要透過 event.target client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {val: ''};
}
handleChange(event){
this.setState({
val:event.target.value
});
console.log(event.target.value);
}
render() {
return (
<div>
<input type="text" onChange={this.handleChange} /><br/>
<h2>{this.state.val}</h2>
</div>
);
}
}
取得input value
在類別裡,要取得 render input 之後輸入的值,一樣使用 event.target.value
client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
Textarea
在React,textarea要以value的屬性來賦予值,例如:
<textarea value='hello world' />
範例
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.handleTxtChange = this.handleTxtChange.bind(this);
this.state = {val: '', txtVal: ''};
}
handleTxtChange(event){
this.setState({
txtVal:event.target.value
});
console.log(event.target.value);
}
render() {
return (
<div>
<textarea value={this.state.txtVal} onChange={this.handleTxtChange} /><br/>
<h2>{this.state.txtVal}</h2><br/>
</div>
);
}
}
select
在 React,select是透過 value 屬性來控制 option
<select value='b' >
<option value="a">A option</option>
<option value="b">B option</option>
</select>
如果 select 是多選(multiple),則只要將陣列值提供給value
<select multiple={true} value={['b', 'c']}>
<option value="a">A option</option>
<option value="b">B option</option>
<option value="c">C option</option>
</select>
範例
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.handleSelChange = this.handleSelChange.bind(this);
this.state = {selVal: 'c'};
}
handleSelChange(event){
this.setState({
selVal:event.target.value
});
console.log(event.target.value);
}
render() {
return (
<div>
<select value={this.state.selVal} onChange={this.handleSelChange}>
<option value="a">A option</option>
<option value="b">B option</option>
<option value="c">C option</option>
<option value="other">Other option</option>
</select>
<br/>
<h2>{this.state.selVal}</h2><br/>
</div>
);
}
}
多 Input 處理方式
在表單中,會使用到多個輸入欄位,或者勾選欄位等… 會透過 name 屬性來幫不同input命名,以及透過 type 來定義欄位屬性
Last name:
<input
name="lastname"
type="input"
value={this.state.lastname}
onChange={this.handleSelChange} /><br/>
sexual:
<input
name="sexual"
type="checkbox"
checked={this.state.sexual}
onChange={this.handleSelChange} /><br/>
當同時存在多個 input,其中type 包括 text, checkbox
底下這範例會說明如何取得各個值,及輸出
以及透過 this.setState({[name]:value});
方式來控制
client/components/App.jsx
CVT2HUGO: state 對應值
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.handleSelChange = this.handleSelChange.bind(this);
this.state = {
firstname: '',
lastname: '',
sexual: true
};
}
handleSelChange(event){
const target = event.target;
const name = target.name;
const value = target.type === 'checkbox' ? target.checked : target.value;
this.setState({
[name]:value
});
}
render() {
return (
<div>
First name:
<input
name="firstname"
type="input"
value={this.state.firstname}
onChange={this.handleSelChange} /><br/>
Last name:
<input
name="lastname"
type="input"
value={this.state.lastname}
onChange={this.handleSelChange} /><br/>
sexual:
<input
name="sexual"
type="checkbox"
checked={this.state.sexual}
onChange={this.handleSelChange} /><br/>
<hr />
<h2>First name:{this.state.firstname}</h2><br/>
<h2>Last name:{this.state.lastname}</h2><br/>
<h2>sexual:{this.state.sexual?'Male':'Female'}</h2><br/>
</div>
);
}
}
提升狀態 (Lifting State Up)
在 React 有一個提升狀態(Lifting State Up)的觀念
也就是,相關聯的component之間要互相傳遞數據及狀態, 統一將這些數據狀態提升到最接近的父層 components, 從這個父層來管理底下各components所需的數據、方法等狀態,
這裡舉例,我們想要建立兩個輸入欄位,並且最後要統計總和
先建立主要的父層 App 物件,接續建立 B 物件(文字輸入框)、C物件(文字輸入框)、Sum物件(總和) 這時透過 App 來負責建構主要的數據及方法,並且在render B、C、Sum同時, 會將這些方法及值各別帶入, 當B、C輸入文字變更狀態,回傳通知App,App會在統整資訊再傳給Sum來顯示總和
client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
this.handleBChange = this.handleBChange.bind(this);
this.handleCChange = this.handleCChange.bind(this);
this.state = {bdata: '',cdata: ''};
}
handleBChange(e) {
console.log(e);
this.setState({bdata: e});
}
handleCChange(e) {
console.log(e);
this.setState({cdata: e});
}
render() {
return (
<div>
<B bdata={this.state.bdata} onBChange={this.handleBChange} />
<C cdata={this.state.cdata} onCChange={this.handleCChange} />
<Sum bdata={this.state.bdata} cdata={this.state.cdata} />
</div>
);
}
}
class B extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onBChange(e.target.value);
}
render() {
return <p>B <input name="b" onChange={this.onChange} /></p>
}
}
class C extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(e) {
this.props.onCChange(e.target.value);
}
render() {
return <p>C <input name="c" onChange={this.onChange} /></p>
}
}
class Sum extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<hr />
{this.props.bdata &&
<p>B值為 {this.props.bdata}</p>
}
{this.props.cdata &&
<p>C值為 {this.props.cdata}</p>
}
{this.props.bdata && this.props.cdata &&
<p>B+C={parseInt(this.props.bdata)+parseInt(this.props.cdata)}</p>
}
</div>
);
}
}
React 提倡元件,而不是繼承
在 React 提供了 components ,讓我們可以很快的透過組件來設計 但是對於剛接觸React的人,可能還會使用繼承(inheritance)的方式,
在Facebook,他們建立了上千個 components 當中,還沒有出現需要使用繼承方式的案例, 透過元件以及props,讓他們能更靈活的客製各種UI及操作行為。
在這裡就是要說明,為什麼要用 components,以及component如何解決inheritance問題
props.children 取得元素物件鑲嵌內容
在React 有一個**特殊案例 (“special cases”)**概念 一個特殊案例元件,可以render出多個 components,並且傳送 props 給這些 components
以接下來的範例來說,對於其他相關聯的 components 而言, App components 就是一個 “special cases” ,會透過props傳送訊息給A,且在render A 時,夾帶了一些元素 相關的components可以透過 props來取得參數, 以及透過props.children來取得鑲嵌在“special cases” render 該物件內的元素。
下面解釋可能會比較清楚這段的用意" 這裡直接用範例來說明 props.children 的用法, 我們建立一個 App component class,並且準備 return 一個 A元素物件,且A元素物件裡包含了一些元素,
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
//傳送一個訊息給A元件
return (
<A specialCasesMsg='hello A~ This is a message from special cases'>---A元件------
----這裡面就是render A時,所夾帶的元素----
<h2>Child element</h2>
<p>Chldren element will pass directly into A components</p>
</A>
);
}
}
A components 只要呼叫 props.children 就可以取得該元素
function A(props) {
return (
<div>
<h1>A components</h1>
<p>{props.specialCasesMsg}</p>
{props.children}
</div>
)
}
client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
//傳送一個訊息給A元件
return (
<A specialCasesMsg='hello A~ This is a message from special cases'>
----這裡面就是render A時,所夾帶的元素----
<h2>Child element</h2>
<p>Chldren element will pass directly into A components</p>
</A>
);
}
}
function A(props) {
return (
<div>
<h1>A components</h1>
<p>{props.specialCasesMsg}</p>
{props.children}
</div>
)
}
porps 傳遞元素物件
由“special cases”來主導,並且讓底下元素物件可以透過 props來互相引用,
這裡舉例,App components可以把新建立的 B, C components,透過 props 直接傳遞給 A components 因此,就能在A裡面用 props 的方式來使用 B, C
當然,B、C也可以用 Class 的方式來建構,功能都不會變~
範例 client/components/App.jsx
/*
./client/components/App.jsx
*/
import React from 'react';
export default class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<A specialCasesMsg='hello A~ This is a message from special cases' getb={<B/>} getc={<C />}>
<h2>Child element</h2>
<p>Chldren element will pass directly into A components</p>
</A>
);
}
}
function A(props) {
return (
<div>
<h1>A components</h1>
<p>{props.specialCasesMsg}</p>
{props.children}
{props.getb}
{props.getc}
</div>
)
}
function B(props){ return ( <p>This is B</p> ) }
function C(props){ return ( <p>This is C</p> ) }
最後,
對於一些非UI功能的操作,建議可以獨立成一個 Module, 需要的components只需要導入這個 Module,而不需要特別用 extends 繼承
錯誤處理
< IE11 時的處理方式
requestAnimationFrame 環境變數支援
Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills https://reactjs.org/docs/javascript-environment-requirements.html https://dev.to/letsbsocial1/requestanimationframe--polyfill-in-react-16-2ce
MAP & SET
yarn add core-js
在主要index.js載入
import 'core-js/es6/map';
import 'core-js/es6/set';
import React from 'react';
import ReactDOM from 'react-dom';
yarn add raf
import 'raf/polyfill';
export default 在IE8的問題
(目前尚未解決) 或許react-scripts-ie8 可以試試看 http://blog.404mzk.com/es6ie8.html https://github.com/iineva/react-scripts-ie8/blob/master/package.json