It's a common pattern in React to wrap a component in an abstraction. The outer component exposes a simple property to do something that might have more complex implementation details.
You can use JSX spread attributes to merge the old props with additional values:
React常見的模式就是抽象地包裹一個組件。外部的組件會暴露一個簡單的屬性,用這個接口來完成更發的事情。你可以使用 JSX spread attributes合并props的值。
<Component {...this.props} more="values" />
(譯者注:就是說,要給組件傳入一個參數,就把props的參數傳進去,...是ES6的語法,合并了props中所有的變量。more是定義的一個屬性)
如果你不使用JSX,你可以用任何object helper例如ES6 Object.assign or Underscore _.extend
React.createElement(Component, Object.assign({}, this.props, { more: 'values' }));
剩下的教程都在解釋如何更好地實踐。它用了JSX和實驗的ECMAScript語法
Manual Transfer 手動傳遞參數
大多數情況下你應該明顯地把屬性傳遞過去,這確保你只能曝光一小部分的內部API,確保可以運行
function FancyCheckbox(props) {
var fancyClass = props.checked ? 'FancyChecked' : 'FancyUnchecked';
return (
<div className={fancyClass} onClick={props.onClick}>
{props.children}
</div>
);
}
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
但是name prop、title prop、onMouseOver的情況下呢?
在JSX中用 ... 傳遞參數
注意:
有時候,傳遞每個屬性是很單調脆弱的。這種情況下,你可以使用destructuring assignment來構建一個未知屬性的集合,在...后列出所有你需要知道的屬性。...other
var { checked, ...other } = props;
This ensures that you pass down all the props EXCEPT the ones you're consuming yourself.
這可以確保你傳遞了所有的props,除了那個你自己用掉的。
function FancyCheckbox(props) {
var { checked, ...other } = props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
// `other` contains { onClick: console.log } but not the checked property
return (
<div {...other} className={fancyClass} />
);
}
ReactDOM.render(
<FancyCheckbox checked={true} onClick={console.log.bind(console)}>
Hello world!
</FancyCheckbox>,
document.getElementById('example')
);
注意:
這個上面的例子,checked prop也是一個有效的DOM屬性,如果你不用這樣用解構賦值,你可能會不可避免地把它也傳遞下去了。
當傳遞未知其他props時請一直使用destructuring解構賦值模式
function FancyCheckbox(props) {
var fancyClass = props.checked ? 'FancyChecked' : 'FancyUnchecked';
// ANTI-PATTERN: `checked` would be passed down to the inner component
return (
<div {...props} className={fancyClass} />
);
}
使用和傳遞同個Prop
如果你的組件想要使用一個屬性但是同時還要傳遞它,你可以通過checked={checked}重新傳遞它。這是更好的傳遞完整props對象的方式,因為它便于重構并且起球--||(是的,lint)
function FancyCheckbox(props) {
var { checked, title, ...other } = props;
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
var fancyTitle = checked ? 'X ' + title : 'O ' + title;
return (
<label>
<input {...other}
checked={checked}
className={fancyClass}
type="checkbox"
/>
{fancyTitle}
</label>
);
}
(譯者注:就是把checked放在屬性欄里傳遞)
注意:
順序很重要!把{...other}放在JSX props的前面,你可以保證你組件的consumer不重寫他們。這個例子中我們確保了input屬性是“checkbox”
Rest and Spread Properties ...
Rest properties可以讓你從一個object提取出剩下的屬性到一個新的object。它排除了其他在解構賦值模式的屬性,這是ECMAScript proposal的
實驗性功能
var { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x; // 1
y; // 2
z; // { a: 3, b: 4 }
注意:
為了用Babel 6轉換rest and spread properties,你需要安裝es2015
preset, the transform-object-rest-spread
plugin, 并且在babellrc文件中配置他們
用Underscore傳遞參數
如果你不用JSX,你可以使用一個library來實現這個功能。Underscore支持supports_.omit
來過濾出屬性和 _.extend復制屬性到一個新的object。
function FancyCheckbox(props) {
var checked = props.checked;
var other = _.omit(props, 'checked');
var fancyClass = checked ? 'FancyChecked' : 'FancyUnchecked';
return (
React.DOM.div(_.extend({}, other, { className: fancyClass }))
);
}
Forms
Form組件例如 <input>, <textarea>, <option>不同于原生組件,因為他們可以通過用戶的交互產生變化。這些組件提供了接口,讓你更簡單地管理form,處理好與用戶的交互。更多關于<form> events的信息 see Form Events.
交互的Props
Form組件支持一部分因為用戶交互受影響的props值
- value, <input> <textarea>支持
- checked, <input> type checkbox or radio
- selected, <option>
在HTML中,<textarea>的值通過children來設置的。React中你應該通過value設置
Form組件允許監聽改變,設置一個callback在onChange prop。onChange prop會在瀏覽器上工作,來觸發用戶響應。當以下幾種情況:
- value <input> <textarea>發生改變
- checked <input>發生改變
- selected <option> 發生改變
像所有的DOM事件一樣,所有原生組件都支持onChange prop,可以用來監聽冒泡事件。
注意:
<input> <textarea> 中,onChange事件被重寫了,用這個不要用DOM內置的那個event handler. oninput
Controlled Components 控制組件
controllledinput有value prop,設置<input>的默認值來自value prop的賦值
render() {
return <input type="text" value="Hello!" />;
}
用戶輸入對渲染的組件毫無影響,因為React賦值了Hello!給value。如果要在response中更新用戶輸入,你可以使用onChange事件
class MyForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'Hello!'};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
);
}
}
這個例子中,我們接受了用戶輸入的值并且更新了<input>的value prop。這個模式可以很簡單的響應或處理用戶的輸入,例如
handleChange(event) {
this.setState({value: event.target.value.substr(0, 140)});
}
這個例子讀取了用戶輸入并且只截取了前140個字符,一個Controlled組件不會一直維持它自己內部的狀態。這個組件單純地基于props進行渲染。
Checkboxes和Radio buttons的潛在問題
請注意,嘗試標準化checkbox和radio Inputs的handle change,React使用了點擊事件代替change事件。大部分情況下它表現正常,除了在change handler里調用 preventDefault的時候,preventDefault會阻止瀏覽器更新input值,即使checked已經被觸發。要解決這個問題,要不刪除掉call preventDefault,要不在setTimeout中啟用觸發checked事件
Uncontrolled組件
渲染input時候會給一個空值,任何用戶輸入會立即反映在渲染的元素上。如果你想要監聽value的變化,你可以像在controlled組件中一樣使用onChange事件。一個uncontrolled組件會維持它自己的內部狀態。
默認值
如果你想要初始化組件為一個非空值,你可以給prop設置一個默認值,例如:
render() {
return <input type="text" defaultValue="Hello!" />;
}
這個例子運行起來會像 Controlled Components 例子那樣,同樣地,<input type="checkbox">和<input type="radio">都支持defaultChecked,select支持defaultValue。
注意:
defaultValue和defaultChecked props值在初始化render中調用。如果你需要在之后的render中更新value,你將需要使用controlled component.
高級話題
Why Controlled Components?
Using form components such as <input>
in React presents a challenge that is absent when writing traditional form HTML. For example, in HTML:
為什么叫Controlled組件?
在React使用傳統的form HTMLform組件例如input會有不存在的問題。例如,HTML中:
<input type="text" name="title" value="Untitled" />
這個render了一個有初始化value值的input。當用戶更新input時候,節點值value屬性會變化。然而,node.getAttribute('value')仍會返回最初初始化的值。不像HTML,React組件必須代表當前時間下的view狀態,不只是初始化的狀態。例如React中:
render() {
return <input type="text" name="title" value="Untitled" />;
}
因為這個方法描述了當前時刻下的view,text input的值應該一直都是Untitled。
為什么Textarea Value?
HTML中,<textarea>的值通過children來設置的
<!-- antipattern: DO NOT DO THIS! -->
<textarea name="description">This is the description.</textarea>
HTML中,這方便用戶輸入多行文字,然而,因為React是JavaScript,我們沒有字符串限制,而且如果要換行可以用\n。總而言之,我們有value和defaultValue屬性,children的角色很難說明白。因此,在使用<textarea>時候你不應該使用children設置value
<textarea name="description" value="This is a description." />
如果你非要用children,它就像defaultValue一樣。
為什么Select Value?
HTML中select選中的<option>一般是通過selected屬性聲明的。React中,為了讓組件更簡單操作,采用了下面的格式:
<select value="B">
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Cranberry</option>
</select>
創建一個uncontrolled組件,使用defaultValue。
注意:
你可以給value屬性傳遞一個數組,允許你在一個select中選中多個options:<select multiple={true} value={['B', 'C']}>
必要操作imperatively operation
如果你需要imperatively執行一個操作(就是你要在dom節點上操作),你必須獲取一個DOM節點的索引reference to the DOM node。例如,如果你想要imperatively提交一個表單,一個辦法就是給form元素附屬一個ref屬性,手動在form.submit()里面調用
與瀏覽器的工作
React provides powerful abstractions that free you from touching the DOM directly in most cases, but sometimes you simply need to access the underlying API, perhaps to work with a third-party library or existing code.
React提供了強大的分離功能,大多數情況下可以把你從直接接觸DOM中解放出來,但有些時候你可以直接訪問底層API, 可能調用第三方庫。
The Virtual DOM 虛擬的DOM
React非常快因為它從來都不直接和DOM對話。React保持了DOM在內存中快速的表現。render() 方法實際上返回的是一段對DOM的描述,React可以比較這段描述與內存上的表現,來計算出最快更新瀏覽器的方式。
另外,React完成了一套完整的合成的時間系統,如果忽略瀏覽器的怪異模式,所有的事件對象都確保遵從W3C聲明,每個事件都很穩定(everything bubbles consistently),在不同瀏覽器上都很搞笑。你甚至可以在舊版不支持的瀏覽器上使用HTML5 事件。
大多數時間你應該待在React的“假瀏覽器”世界,因為者更高效并且更簡單。但是,有時候你需要跳出來去訪問底層的API,可能需要與第三方庫合作,如jQuery。React提供了逃生窗口給你,來直接調用底層DOM API。
Refs and findDOMNode()
為了和瀏覽器交互,你將需要引用一個DOM節點。你可以給任何元素附屬一個ref屬性,它可以使你引用組件的backing instance。這很有用如果你需要調用組件的imperative functions, 或者想要訪問底層的DOM節點。了解更多refs,包括使用的方式,請看refs to components documentation.
組件周期
Components have three main parts of their lifecycle:
Mounting: A component is being inserted into the DOM.
Updating: A component is being re-rendered to determine if the DOM should be updated.
Unmounting: A component is being removed from the DOM.
React provides lifecycle methods that you can specify to hook into this process. We providewill methods, which are called right before something happens, and did methods which are called right after something happens.
組件有三個主要的生命周期:
- Mounting: 組件即將插入DOM.
- Updating: 組件即將重新渲染,判斷是否要更新DOM
- Unmounting:組件即將從DOM移除
Mounting
-
getInitialState()
: 在組件mount之前被調用,有state的組件應該實現這個方法并且返回最初的state data -
componentWillMount()
:在mouting之前被調用 -
componentDidMount()
: 在mouting之后被調用。初始化需要的DOM節點放在這里
Updating
componentWillReceiveProps(object nextProps)
: 當一個已經掛載的組件接收到新的props的時候調用,這個方法應該被用來比較this.props和nextProps來實現state轉換(用this.setState()方法)souldComponentUpdate(object nextProps, object nextState): boolean
當一個組件決定是否發生了應該去更新DOM的變化。實現這個方法的最佳辦法就是比較this.props和nextProps, this.state和nextState。 如果React跳過updating,則 return falsecomponentWillUpdate(object nextProps, object nextState)
在updating之前被調用,你不能在這里調用this.setState()componentDidUpdate(object prevProps, object prevState)
在updating之后被調用
Unmounting
componentWillUnmount()
在一個組件unmounted和destroyed之前。Cleanup應該放在這里
Mounted方法
Mounted復合的組件還支持下面的方法:
component.forceUpdate()
可以在任何掛載的組件上調用,當你知道更深層的組件狀態已經改變并且不使用this.setState()方法。
瀏覽器支持
React支持大多數瀏覽器,包括IE9及以上(不支持那些不支持ES5的瀏覽器)。
8.2 Refs to Components
建立了你的component之后,你可能發現想要“reach out”,調用組件變量從render() 返回的方法。大多數情況下,這是沒必要的因為React數據流確保了最新的props被送給render()輸出的每個child。然而,在一些情況下這還是有必要的或者有好處的,所以React提供了安全艙口,叫做refs。當你需要找到組件渲染的DOM標記,在非React項目中使用React組件、或者將你當前的代碼轉換成React,這些refs(references)特別有用。讓我們看看如何得到一個ref,以下是個完整例子。
從 ReactDOM.render返回的ref
不要和你在組件內定義的render()方法(它返回的是一個虛擬DOM)混淆。ReactDOM.render()將會返回一個組件的 backing instance(or null for stateless components).的reference索引。
var myComponent = ReactDOM.render(<MyComponent />, myContainer);
記住,JSX不會返回一個組件變量!它只是一個React元素。一種輕量的表達方式說明React已經掛載的組件是什么模樣。
var myComponentElement = <MyComponent />; // This is just a ReactElement.
// Some code here...
var myComponentInstance = ReactDOM.render(myComponentElement, myContainer);
myComponentInstance.doSomething();
注意:
它只能再頂層使用。在組件內部,讓你的props和state來處理和子組件的數據傳遞,或者使用其他方法(string attribute or callbacks)獲取ref
The ref Callback Attribute
React支持一種你可以附屬在任何組件上的特殊的屬性。ref屬性可為一個callback function,callback會在組件 mount后立即執行。引用的組件會作為一個參數傳遞過去,callback function可以使用這個組件,或者儲存起來作為reference將來使用。
簡單地給render返回的任何東西添加ref值
render: function() {
return (
<TextInput
ref={function(input) {
if (input != null) {
input.focus();
}
}} />
);
},
當給一個DOM組件<div />
附屬一個ref值時,你會得到返回的DOM節點,當附屬一個ref到復合的組件<TextInput />時,你會得到React class實例。之后,如果任何參數在class定義中暴露出來,你可以調用那個組件的方法。
當給一個DOM組件 <div />
附屬一個ref值時,你會得到返回的DOM節點,當附屬一個ref到復合的組件<TextInput />時,你會得到React class實例。之后,如果任何參數在class定義中暴露出來,你可以調用那個組件的方法。
請注意當引用的組件unmounted卸載后,無論ref發生任何改變,調用舊的ref的值為null。這種情況是為了防止在儲存實例的時候內存泄漏(第二個例子就說明了這點)。還要注意到,像例子那樣在inline function表達式中使用當使用refs時,React在每次update時把每個function object看做是不同的,在調用組件實例之前,ref的值為null。
The ref String Attribute
注意:
盡管string refs沒有被棄用,被視為合法的,但是在不就的將來可能會被廢除。盡量使用Callback refs。
React還支持使用string(非callback)作為ref prop。
-
將ref屬性賦值給任何從render返回的東西。例如:
<input ref="myInput" />
-
另一些代碼(通常是event handler ),通過this.refs訪問backing instance
var input = this.refs.myInput;var inputValue = input.value;
var inputRect = input.getBoundingClientRect();
A Complete Example
In order to get a reference to a React component, you can either use this
to get the current React component, or you can use a ref to get a reference to a component you own. They work like this:
一個完整的例子
為了得到React組件的例子,你可以使用這個來得到當前的React組件,或者你可以使用ref得到組件的引用reference。例如
var MyComponent = React.createClass({
handleClick: function() {
// Explicitly focus the text input using the raw DOM API.
if (this.myTextInput !== null) {
this.myTextInput.focus();
}
},
render: function() {
// The ref attribute is a callback that saves a reference to the
// component to this.myTextInput when the component is mounted.
return (
<div>
<input type="text" ref={(ref) => this.myTextInput = ref} />
<input
type="button"
value="Focus the text input"
onClick={this.handleClick}
/>
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
這個例子中,我們得到了text input backing instance 的reference,還在button點擊的時候調用了focus()。對于符合的組件,reference指component class的實例,所以你能調用class定義的任何方法。如果你需要訪問組件的底層的DOM節點,你可以使用ReactDOM.findDOMNode 作為"escape hatch" 但是我們不推薦這么做,因為它會打破封裝,并且大多數情況下有種更清晰的方式在React model中規范的你的代碼結構。
Summary 總結
如果不方便通過streaming Reactive props和state傳遞信息到child instance,Refs是非常棒的方式。然后,他們不應該是你程序的數據流的抽象定位(go-to abstraction for flowing data through your application.)???我們默認使用React數據流,避免使用refs,因為refs沒繼承react數據流。
好處:
你可以在組件class定義任何公共的方法(例如Typeahead的reset方法),通過refs調用這些公共的方法(例如this.refs.myTypeahead.reset()).大多數情況下,使用React內置的數據流比refs更清楚。
執行DOM測量performing DOM measurements經常需要使用原生的組件(例如<input />),使用ref訪問底層的DOM節點。Refs是眾多可實現這一操作的可靠方法之一。
Refs會自我管理。如果這個child destroyed,它的ref也會自動銷毀。不用擔心此處的內存處理(除非你瘋了想要保留reference)
注意:
不要在任何組件的render方法內部訪問refs,或者當然和組件的reder方法被調用的時候。
如果你想要保留Google Closure Compiler advanced-mode crushing resilience,確保不要把ref當做字符串屬性來訪問,如果你的ref定義為ref="myRefString", 你必須使用this.refs['myRefString']。
如果你沒有React寫過好幾個項目有,你會常常想使用refs來實現你app的效果。如果是這種情況,花一點時間來冷靜思考下在組件的繼承中,哪里應該使用state。經常,這回很清楚,state合適被放在更高level的繼承關系中。把state放在那里經常會減少你去使用ref來實現效果的欲望。數據流就會幫你實現目標。
Refs不可以被附屬到無state的function中,因為組件沒有backing instance。你可以把它包裹在沒有state的標準復合組件中,并且附屬ref給這個復合組件。
Tooling Integration 工具整合
我們已經盡可能讓React和環境無關。大家可以使用多種語言(JavaScript,TypeScript, ClojureScript)書寫React,在多種環境下(web, iOS,Android,NodeJS,Nashorn)使用React。有許多工具可以幫助你建立應用程序。下面的部分我們介紹一些常用的React工具。
Language Tooling 描述了如何使用工具例如Babel來轉化JSX語言,獲得更好的開發體驗。
Package Management 描述了如何配置React項目依賴。
Server-side Environments 描述了如何配置使用React服務端環境。
Language Tooling
省略
Package Management
CDN-hosted React 使用CDN
我們提供了CDN服務 [on our download page (https://facebook.github.io/react/downloads.html)。這些內置的文件都是用UMD模塊格式。把他們扔進<script>即可開始書寫React和ReactDOM globals。在CommonJS和AMD環境中也生效。
Using React from npm
你可以在CommonJS模塊系統,例如browserify or webpack使用React。使用Use thereact
and react-dom
npm packages.
// main.js
var React = require('react');
var ReactDOM = require('react-dom');
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
用.babelrc文件配置 babel
{ "presets": ["react"] }
注意:
如果你使用ES2015,你需要使用 babel-preset-es2015
使用browserify安裝React DOM 簡歷你的bundle 捆綁:
$ npm install --save react react-dom babelify babel-preset-react
$ browserify -t [ babelify ] main.js -o bundle.js
webpack:
$ npm install --save react react-dom babel-preset-react babel-loader babel-core
$ webpack main.js bundle.js --module-bind 'js=babel-loader'
注意:
如果你使用ES2015,你需要使用 babel-preset-es2015
注意。默認地,React會處于開發者模式,它有點慢,但是利于開發。如果你想使用production模式,設置環境變量NODE_ENV為production (使用 envify or webpack's DefinePlugin)
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
});
更新你的HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
</head>
<body>
<div id="example"></div>
<script src="bundle.js"></script>
</body>
</html>
Bower中使用React
Bower是一個為前端開發優化的軟件包管理器。如果一個package依賴多個packages,例如jQuery,Bower只會下載jQuery一次。這就是所謂的單個依賴關系圖,它會幫助你減少頁面負載。更多信息請查看http://bower.io/.
如果你想要使用bower,很簡單
bower install --save react
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="bower_components/react/react.js"></script>
<script src="bower_components/react/react-dom.js"></script>
<script src="https://unpkg.com/babel-core@5.8.38/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
</body>
</html>
Using master
We have instructions for building from master
in our GitHub repository.
Server-side Environments
Add-ons 擴展
The React add-ons are a collection of useful utility modules for building React apps. These should be considered experimental and tend to change more often than the core.
React擴展是關于一些創建React app功能模塊的集合。這些是實驗室性的功能,可能將來會變化。
TransitionGroup
and CSSTransitionGroup
, 處理不容易實現的animations和transitions,例如組件移除之前。LinkedStateMixin
, 簡化用戶表單的input數據和組件state的協調cloneWithProps
, 制作React組件的淺向復制,并更改他們的props。createFragment
, 制作一套外部的externally-keyed的children。update
, 一個幫助JavaScript更簡單地處理不可改變數據的function。PureRenderMixin
, 某些情況下的性能加速器shallowCompare
, 一個幫助function,淺性比較一個組件中的props和state,來決定它是否應該被更新。
The add-ons below are in the development (unminified) version of React only:
下面的擴展是在development version的React才能使用
TestUtils
, simple helpers for writing test cases.
Perf
, a performance profiling tool for finding optimization opportunities.
想要獲取擴展,從npm(npm安裝react-addons-pure-render-mixin)單個安裝他們。不支持除npm以外的工具。
Animation 動畫
React提供ReactTransitionGroup 擴展組件,作為動畫低級的API。ReactCSSTransitionGroup可以簡單地完成基礎的CSS動畫和轉場transitions。
高級的API: ReactCSSTransitionGroup
ReactCSSTransitionGroup是基于ReactTransitionGroup,在React組件進入或離開DOM的時候,幫助你很簡單的完成CSS transitions 和 animations。啟發于庫ng-animate .
開始
ReactCSSTransitionGroup 是ReactTransitions的接口。這是個簡單的元素,可以包裹所有的你感興趣的想要加上動畫的組件。這個例子是將列表中的項目淡入和淡出。
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = {items: ['hello', 'world', 'click', 'me']};
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd() {
var newItems = this.state.items.concat([
prompt('Enter some text')
]);
this.setState({items: newItems});
}
handleRemove(i) {
var newItems = this.state.items.slice();
newItems.splice(i, 1);
this.setState({items: newItems});
}
render() {
var items = this.state.items.map((item, i) => (
<div key={item} onClick={() => this.handleRemove(i)}>
{item}
</div>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</ReactCSSTransitionGroup>
</div>
);
}
}
注意:
你必須給所有ReactCSSTransitionGroup的children提供the key
attribute ,即使在渲染一個單獨的item時,也要提供。這就是React如何決定哪個children進入, 離開,或者留下。
在這個組件中,當一個新的item加入到ReactCSSTransitionGroup時,它會生成example-enter CSS class, example-enter-active CSS class就被添加到下一個時刻中。這是根據transitionName prop生成的協定。你可以使用這些classes觸發CSS animation or transition。例如,試著添加這段CSS并且增加一個新的item
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 500ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
你會注意到animation durations需要在CSS和render 方法中同時聲明。這告訴React什么時候刪掉元素的animation classes。如果這個元素離開,從DOM中刪掉這個元素。
Animate Initial Mounting 讓最初的Mouting動起來
ReactCSSTransitionGroup提供了可選的prop transitionAppear,可以在最初組件mount的時候增加額外的transition階段。通常沒有在最初的mount時候transition phasis,因為transitionAppear的默認值是false。接下來的案例說明了誰向prop transitionAppear 傳遞了 true的值。
render() {
return (
<ReactCSSTransitionGroup
transitionName="example"
transitionAppear={true}
transitionAppearTimeout={500}>
<h1>Fading at Initial Mount</h1>
</ReactCSSTransitionGroup>
);
}
During the initial mount ReactCSSTransitionGroup
will get the example-appear
CSS class and the example-appear-active
CSS class added in the next tick..
在最初的mount期間,ReactCSSTransitionGroup將會得到example-appear CSS class和加入下一時刻的example-appear-active CSS class
.example-appear {
opacity: 0.01;
}
.example-appear.example-appear-active {
opacity: 1;
transition: opacity .5s ease-in;
}
在最初的mount,所有ReactCSSTransitionGroup的children會出現appear,而不是進入enter。之后, 所有的加入到ReactCSSTransitionGroup的children,開始enter,而不是appear。
注意:
prop transitionAppear是在版本0.13加入到ReactCSSTransitionGroup的。為了維持向后兼容,默認值為false
Custom Classes 自定義Classes
在transitions的每一步還可以使用自定義的class名字。不要傳遞string給transitionName,你可以傳遞一個包含enter leave class names Object, 或者包含enter, enter-active, leave-active, leave class names. 只要enter leave classes提供了,enter-active和leave active classes會自動加上-active后綴。這里有兩個例子
...
<ReactCSSTransitionGroup
transitionName={ {
enter: 'enter',
enterActive: 'enterActive',
leave: 'leave',
leaveActive: 'leaveActive',
appear: 'appear',
appearActive: 'appearActive'
} }>
{item}
</ReactCSSTransitionGroup>
<ReactCSSTransitionGroup
transitionName={ {
enter: 'enter',
leave: 'leave',
appear: 'appear'
} }>
{item2}
</ReactCSSTransitionGroup>
...
Animation Group Must Be Mounted To Work
為了讓children也能有transitions, ReactCSSTransitionGroup必須已經掛載到DOM上,或者prop transitionAppear設置為true。
下面的例子無法工作,因為ReactCSSTransitionGroup正在mounted新item,而不是這個新item已經掛載上去了。和Getting Started 這個比較一下看看區別。
render() {
var items = this.state.items.map((item, i) => (
<div key={item} onClick={() => this.handleRemove(i)}>
<ReactCSSTransitionGroup transitionName="example">
{item}
</ReactCSSTransitionGroup>
</div>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
{items}
</div>
);
}
(譯者注:嗯,我覺得就是要寫在return里。)
Animating One or Zero Items,讓1或0個item動起來
上面這個例子,我們在ReactCSSTransitionGroup渲染了一個列表的items。然而,ReactCSSTransitionGroup 的children可能是1個或0個,這就需要單個元素進入和離開的動畫。相似的,你可以animate新元素取代當前元素。例如,我們可以完成一個簡單的輪播:
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
function ImageCarousel(props) {
return (
<div>
<ReactCSSTransitionGroup
transitionName="carousel"
transitionEnterTimeout={300}
transitionLeaveTimeout={300}>
<img src={props.imageSrc} key={props.imageSrc} />
</ReactCSSTransitionGroup>
</div>
);
}
禁止動畫
你可以禁止進入或離開的動畫。例如,有時候你可能想要enter動畫不要leave動畫,但是ReactCSSTransitionGroup在離開DOM節點之前等待著一個動畫完成。你可以在 ReactCSSTransitionGroup設置addtransitionEnter={false} 或 transitionLeave={false} props來禁止動畫
注意:
當使用 ReactCSSTransitionGroup要注意,當一個transition結束、或者要展示邏輯復雜的動畫的時候。如果你想要更精準的控制,你可以使用低級的ReactTransitionGroup API,它更提供了你需要在自定義transition時使用的hooks
Low-level API: ReactTransitionGroup 低級API
ReactTransitionGroup是動畫的基礎。require('react-addons-transition-group')加上這句就可以使用了。當children從中添加或刪除時,特殊的生命周期hooks會被調用。
componentWillAppear(callback)
它和componentDidMount()調用的時間一樣,針對那些最初mount進TransitionGroup的組件。它會阻斷其他動畫的執行,直到callback函數被調用。只在 TransitionGroup最初的render中調用。componentDidAppear()
在callback function之后調用,callback被傳遞到 componentWillAppearcomponentWillEnter(callback)
與componentDidMount()調用的時間一樣,當組件唄加入到已經存在的TransitionGroup的時候。它會阻斷其他執行動畫直到callback被調用。在最初的TransitionGroup 的render中不被調用。componentDidEnter()
在callback function之后調用,被傳遞到componentWillEntercomponentWillLeave(callback)
當child從ReactTransitionGroup刪掉的時候調用。盡管child已經走了,ReactTransitionGroup還是在DOM中保留它直到callback被調用。componentDidLeave()
當wiilLeave callback被調用,和componentWillUnmount同
Rendering a Different Component 渲染一個不同的組件
默認ReactTransitionGroup 作為一個span渲染。你可以通過提供component prop改變這點。例如,這是你如何render <ul>
<ReactTransitionGroup component="ul">
...
</ReactTransitionGroup>
任何附加的、用戶定義的屬性會成為已經渲染的組件的屬性。例如,這是你如何render 有CSS class的<ul>
<ReactTransitionGroup component="ul" className="animated-list">
...
</ReactTransitionGroup>
每個React可以render的DOM組件都可以使用。然而, 組件不需要是一個DOM組件。可以是任何你想要的組件,即使是你自己寫的。只要寫component={List}, 你的組件將會接收this.props.children
Rendering a Single Child 渲染單獨的child
人們經常使用ReactTransitionGroup來動畫一個單獨child的 mouting和unmounting,例如折疊面板。通常ReactTransitionGroup在一個span里包裹了所有的children(或者一個自定義的組件)。這是因為任何React組件都必須返回一個single root元素,并且ReactTransitionGroup沒有拋出異常的規則。然而,如果你只需要在ReactTransitionGroup里render一個single child, 你完全沒必要包裹在<span>里,或者任何DOM組件。要實現這個,創建一個自定義組件,render 第一個child并且直接傳遞它。
function FirstChild(props) {
var childrenArray = React.Children.toArray(props.children);//為什么要toArray?
return childrenArray[0] || null;
}
現在你可以在 <ReactTransitionGroup> props申明第一個child作為組件的prop,在result DOM中避免任何wrappers
<ReactTransitionGroup component={FirstChild}>
{someCondition ? <MyComponent /> : null}
</ReactTransitionGroup>
這只在你animating 一個single child in and out時生效,例如折疊面板。當animating多個children或替換一個child為另一個(例如輪播)這種方法不生效。對于一個圖片輪播,當當前的圖片animating out,另一個圖片將會animate in。所以 <ReactTransitionGroup> 需要給他們一個通用的DOM parent。你不能避免wrapper多個children,但是你可以在component prop自定義這個wrapper
Context
React最大的優勢之一就是,很容易通過React組件去追蹤數據流。當你查看一個組件時,你可以很容易地看出哪個props被傳遞過來,這使得你的app易讀懂。
偶爾,你想要在么女額level上,手動通過組件tree,而不通過props傳遞數據。React的“context”特性就在這里。
注意:
Context是一個先進的實驗性特征。API可能會在將來改變。
大多數應用都不需要使用context。特別是如果你只是剛開始使用React,你可能不需要使用context。使用context會使你的代碼更難理解,因為它讓數據流變得不明顯。這類似于在你的應用中使用全局變量傳遞state。
如果你非要使用content,盡可能少用且不論你是否在簡歷app或者庫,盡量在一個小區域分離出你對context的使用,并且避免直接使用context API,這樣你在升級API的時候容易操作。
Passing info automatically through a tree 從一個tree自動傳遞信息
假設你有這樣的結構:
class Button extends React.Component {
render() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
}
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button color={this.props.color}>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
render() {
const color = "purple";
const children = this.props.messages.map((message) =>
<Message text={message.text} color={color} />
);
return <div>{children}</div>;
}
}
在這個例子中,我們手動通過一個color prop來設置Button和Message組件的style。當你想要整個suvbtree都訪問信息(color),Theming 索引題目 是一個很好的例子。使用context我們可以自動在tree中傳遞this。
class Button extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
class Message extends React.Component {
render() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
}
class MessageList extends React.Component {
getChildContext() {
return {color: "purple"};
}
render() {
const children = this.props.messages.map((message) =>
<Message text={message.text} />
);
return <div>{children}</div>;
}
}
MessageList.childContextTypes = {
color: React.PropTypes.string
};
通過在MessageList(the context provider)添加childContextTypes和getChildContent,React自動向下傳遞了信息,任何在subtree中的組件都可以通過contextTypes訪問它。
如果contextTypes沒有定義,context將會為空object。
Parent-child coupling 相互依賴的父子
Context還能讓你建立API
<Menu>
<MenuItem>aubergine</MenuItem>
<MenuItem>butternut squash</MenuItem>
<MenuItem>clementine</MenuItem>
</Menu>
通過在Menu組件向下傳遞相關信息,每個Menu Item 都可以和Menu組件交流。
在你用這個API建立組件之前,考慮下是否有更好的選擇。我們推薦你簡單地以數組形式傳遞items
<Menu items={['aubergine', 'butternut squash', 'clementine']} />
回憶下你還可以在props中傳遞整個React組件
Referencing context in lifecycle methods 在生命周期方法內引用context
如果contextTypes在一個組件內被定義,下面的lifecycle方法將會受到額外的參數,context object:
void componentWillReceiveProps(
object nextProps, object nextContext
)
boolean shouldComponentUpdate(
object nextProps, object nextState, object nextContext
)
void componentWillUpdate(
object nextProps, object nextState, object nextContext
)
void componentDidUpdate(
object prevProps, object prevState, object prevContext
)
Referencing context in stateless functional components 在沒有state的組件中引用context
無狀態的功能組件還可以reference context如果contextTypes被定義為function的屬性。下面的代碼展示了作為無狀態的Button組件。
const Button = ({children}, context) =>
<button style={{background: context.color}}>
{children}
</button>;
Button.contextTypes = {color: React.PropTypes.string};
Updating context 更新context
getChildContext function在state或props改變的時候被調用。為了更新context的數據,用this.setState觸發本地的state更新。這會觸發一個新的context,children會接收到這些變化。
class MediaQuery extends React.Component {
constructor(props) {
super(props);
this.state = {type:'desktop'};
}
getChildContext() {
return {type: this.state.type};
}
componentDidMount() {
const checkMediaQuery = () => {
const type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile';
if (type !== this.state.type) {
this.setState({type});
}
};
window.addEventListener('resize', checkMediaQuery);
checkMediaQuery();
}
render() {
return this.props.children;
}
}
MediaQuery.childContextTypes = {
type: React.PropTypes.string
};
When not to use context 什么時候不用context
就像在書寫清晰代碼時全局變量最好避免使用,在多數情況下你應該避免使用context。特別是,使用前再三考慮,來節省打印時間,用props代替。
context最好的使用案例就是暗中地傳遞已登錄的用戶、當前的語言、或主題信息。所有可能本來是真的全局變量,但是context讓你把范圍局限到單個React subtree。
不要使用context在組件間傳遞你的model data。在tree中明顯地傳遞你的數據更容易被理解。使用context會使你的組件更耦合,不利于重復利用,因為根據render的內容不同,表現可能大不一樣。
Known limitations 了解限制
如果一個組件變化提供了context value,如果在shouldComponentUpdate時,中間的parent return false,使用這個值的子節點不會更新。See issue#2517