React的模式,技術,技巧
React進階的文檔,需要對React有一定了解,很適合想深入React的人閱讀,
Stateless Functions
無狀態組件意思就是組件內無state和生命周期,無需考慮組件的檢測和內存分配問題,是一個改善React性能的好辦法。
import {PropTypes, ContextTypes} from "react";//最簡單的無狀態組件const Greeting = () => <div>Hi there!</div>;//可以接受兩個參數props和contextconst Greeting = (props, context) => <div stylex={{color: context.color}}>Hi {props.name}</div>;//定義局部變數const Greeting = (props, context) => { const style = { fontWeight: "bold", color: context.color }; return <div stylex={style}>{props.name}</div>};//或者定義一個外部函數const getStyle = context => ({ fontWeight: "bold", color: context.color});const Greeting = (props, context) => <div stylex={getStyle(context)}>{props.name}</div>;//然後定義 defaultProps, propTypes 和 contextTypes.Greeting.propTypes = { name: PropTypes.string.isRequired};Greeting.defaultProps = { name: "Guest"};Greeting.contextTypes = { color: PropTypes.string};
JSX Spread Attributes
JSX分離屬性,這是JSX的特點,可以通過…操作符把對象所有屬性傳遞到JSX屬性。
//下面兩句是對等的let main = () => <main className="main" role="main">{children}</main>;let main = () => <main {...{className: "main", role: "main", children}} />;//直接把props轉換成JSX,這個很常用let FancyDiv = (props) => <div className="fancy" {...props} />;let FancyDiv = () => <FancyDiv data-id="my-fancy-div">So Fancy</FancyDiv>;//需要注意的是屬性順序,假如已經存在同名的屬性,後面的屬性會覆蓋前面的屬性let FancyDiv = () => <FancyDiv className="my-fancy-div"/>;const FancyDiv = props => <div {...props} className="fancy"/>;//下面比較有意思,把className和其他剩餘的props單獨來傳遞const FancyDiv = ({ className, ...props }) => ( <div className={["fancy", className].join( )} {...props} />);
Destructuring Arguments
解構參數,很適合使用在無狀態組件中的props解構。
//傳遞所有propsconst Greeting = props => <div>Hi {props.name}!</div>;//傳遞props中的name屬性const Greeting = ({ name }) => <div>Hi {name}!</div>;//通過剩餘參數語法,把name從props中獨立出來const Greeting = ({ name, ...props }) => Hi {name}!;//結合上面的分離屬性使用效果更佳const Greeting = ({ name, ...props }) => <div {...props}>Hi {name}!</div>;
Conditional Rendering
巧用條件渲染
//條件成立時渲染function render() { return condition && <span>Rendered when `truthy`</span>}//條件不成立時渲染function render() { return condition || <span>Rendered when `falsey`</span>}//單行三元運算符function render() { return condition ? <span>Rendered when `truthy`</span> : <span>Rendered when `falsey`</span>}//多行三元運算符function render() { return condition ? ( <span> Rendered when `truthy` </span> ) : ( <span> Rendered when `falsey` </span> )}//也有一種情況,就是使用非布爾值去條件渲染const Oops = ({showFirst, dontShowSecond}) => ( <div> {showFirst && first} {dontShowSecond || second} </div>)//使用0或1傳值會返回"01", 而不是期望的<Oops showFirst={0} dontShowSecond={1}/>//避免這種情況可以使用!!const Oops = ({showFirst, dontShowSecond}) => ( <div> {!!showFirst && first} {!!dontShowSecond || second} </div>)
Children Types
React可以渲染很多數據類型,其中最常見的就是字元串和數組了
//字元串function render() { return ( <div> Hello World! </div> )}//數組--下面這句在開發模式會報錯,因為沒有為數組設key值function render() { return ( <div> {["Hello ", World, "!"]} </div> )}//函數function render() { return ( <div> { (() => "hello world!")() } </div> )}
Array As Children
這是React渲染列表最常用的方式,JSX有直接渲染數組的能力
//下面兩個渲染結果是對等的(<ul> {["first", "second"].map((item) => ( <li>{item}</li> ))}</ul>)(<ul> {[ <li>first</li>, <li>second</li> ]}</ul>)//應用剩餘參數語法分類屬性效果更佳(<ul> {arrayOfMessageObjects.map(({ id, ...message }) => <Message key={id} {...message} /> )}</ul>)
Function As Children
//然而並不常用,這種函數邏輯應該在return之前定義好<div>{() => { return "hello world!" }()}</div>
Render Callback
這是一個使用渲染回調的組件,這不是有用的,但這是一個很容易的例子。
//把children設成一個函數const Width = ({ children }) => children(500)//調用(<Width> {width => <div>window is {width}</div>}</Width>)//輸出結果<div>window is 500</div>//一個更複雜的例子class WindowWidth extends React.Component { constructor() { super(); this.state = { width: 0 }; } //沒想到this.setState有第二參數,並且是函數 componentDidMount() { this.setState({ width: window.innerWidth }, () => { window.addEventListener("resize", ({target}) => this.setState({width: target.innerWidth})) }) } render() { return this.props.children(this.state.width); }}//調用(<WindowWidth> {width => <div>window is {width}</div>}</WindowWidth>)
Children types
一般向子組件傳遞數據用props,但同時也可以用children
//假如存在一個需要返回children的組件class SomeContextProvider extends React.Component { getChildContext() { return {some: "context"} } render() { // 最好返回children的方式是什麼 }}//選項1:返回一個div包裹return <div>{this.props.children}</div>;//選項2:會報錯return this.props.children;//選項3:使用React提供的React.Children可以更合適的處理return React.Children.only(this.props.children);//Children里傳遞另一個組件class App extends React.Component { render() { var title = <h1>Hello there!</h1>; return ( <Header title={ title }> <Navigation /> </Header> ); }}export default class Header extends React.Component { render() { return ( <h1> { this.props.title } <hr /> { this.props.children } </h1> ); }};//Children里傳遞一個對象function UserName(props) { return ( <div> <b>{props.children.lastName}</b> {props.children.firstName} </div> );}function App() { var user = { firstName: Vasanth, lastName: Krishnamoorthy }; return( <UserName>{ user }</UserName> )}//Children里傳遞一個函數function TodoList(props) { const renderTodo = (todo, i) => { return ( <li key={ i }> { props.children(todo) } </li> ); }; return ( <section className=main-section> <ul className=todo-list>{ props.todos.map(renderTodo)}</ul> </section> );}function App() { const todos = [ { label: Write tests, status: done }, { label: Sent report, status: progress }, { label: Answer emails, status: done } ]; var isCompleted = todo => todo.status === done; return ( <TodoList todos={ todos }> { todo => isCompleted(todo) ? <b>{ todo.label }</b> : todo.label } </TodoList> );}
Proxy Component
使用代理組件
//常見的button<button type="button">//使用一個高階的組件代理上面低階的組件const Button = props => <button type="button" {...props}>//調用<Button /><Button className="CTA">Send Money</Button>
Style Component
//假如我們需要一個可自定義的按鈕組件<button type="button" className="btn btn-primary">//定義組件const PrimaryBtn = props => <Btn {...props} primary/>const Btn = ({ className, primary, ...props }) => ( <button type="button" className={classnames( "btn", primary && "btn-primary", className )} {...props} />);//下面輸出同樣的結果<PrimaryBtn /><Btn primary/><button type="button" className="btn btn-primary"/>
Event Switch
事件切換
//分別定義事件handleClick() { require("./actions/doStuff")(/* action stuff */) }handleMouseEnter() { this.setState({ hovered: true }) }handleMouseLeave() { this.setState({ hovered: false }) }//使用switchhandleEvent({type}) { switch(type) { case "click": return require("./actions/doStuff")(/* action dates */) case "mouseenter": return this.setState({ hovered: true }) case "mouseleave": return this.setState({ hovered: false }) default: return console.warn(`No case for event type "${type}"`) }}//使用對象const handlers = { click: () => require("./actions/doStuff")(/* action dates */), mouseenter: () => this.setState({ hovered: true }), mouseleave: () => this.setState({ hovered: false }), default: () => console.warn(`No case for event type "${type}"`)};handleEvent({type}) { const NORMALIZED_TYPE = type.toLowerCase(); const HANDLER_TO_CALL = NORMALIZED_TYPE in handlers ? NORMALIZED_TYPE : default; handlers[HANDLER_TO_CALL].bind(this)();}//巧妙利用try..catchhandleEvent({type}) { try { handlers[type.toLowerCase()].bind(this)(); } catch (e) { handlers[default].bind(this)(); }}
Layout Component
布局組件和內容組件的分離可以更好的控制頁面
//把組件當做props傳遞<HorizontalSplit leftSide={<SomeSmartComponent />} rightSide={<AnotherSmartComponent />}/>class HorizontalSplit extends React.Component { render() { <FlexContainer> <div stylex={{ flex: 1 }}>{this.props.leftSide}</div> <div stylex={{ flex: 2 }}>{this.props.rightSide}</div> </FlexContainer> }}
Container Component
容器組件和UI組件的分離,讓容器組件專註於數據的拉取,UI組件專註於展示的復用。
//一個可復用的UI組件const CommentList = ({ comments }) => <ul> {comments.map(comment => <li>{comment.body}-{comment.author}</li> )} </ul>;//一個拉取數據的容器組件class CommentListContainer extends React.Component { constructor() { super(); this.state = {comments: []} } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: json, success: comments => this.setState({comments: comments}); }); } render() { return <CommentList comments={this.state.comments}/> }}
Higher Order Component
高階組件是把一個組件當做一個參數傳入一個函數,然後返回一個新的組件,用於統一為一些組件執行同樣的工作,是一個非常實用的功能。
//定義一個函數,傳入一個組件,返回一個新組件export var Enhance = ComposedComponent => class extends Component { constructor() { this.state = { data: null }; } componentDidMount() { //常用於拉取數據,改變對應組件的state this.setState({ data: Hello }); } render() { return <ComposedComponent {...this.props} data={this.state.data} />; }};//調用class MyComponent { render() { //判斷是否含有data,沒有則顯示等待中 if (!this.props.data) return <div>Waiting...</div>; //有則返回數據 return <div>{this.props.data}</div>; }}//注意這裡導出的格式export default Enhance(MyComponent);
State Hoisting
狀態提升是指把子組件的數據傳到父組件,一般通過函數來交流。
//傳遞一個函數到子組件class NameContainer extends React.Component { render() { return <Name onChange={newName => alert(newName)}/> }}//在子組件觸發該函數,父組件會反映出來,這是最常見的方式。const Name = ({ onChange }) => <input onChange={e => onChange(e.target.value)}/>//下面就是子組件通過函數傳遞數據到父組件的例子class NameContainer extends React.Component { constructor() { super() this.state = {name: ""} } render() { return <Name onChange={newName => this.setState({name: newName})}/> }}
Controlled – Uncontrolled Components
受控 – 不受控組件,這個概念有點抽象,主要討論from表單里的input元素是個不可控組件,大概了解一下即可。
//當這樣寫的時候,input只能顯示最初獲取的值,且無法改變class UncontrolledNameInput extends React.Component { constructor() { super(); this.state = {name: ""} } render() { return <input type="text" value={this.state.name} /> }};//這樣可以做到雙向綁定,讓input變得可控//只要知道一點,當你按下鍵盤的時候,實際上是先改變this.state.name,才會間接引起input的值變化,並非直接改變。class ControlledNameInput extends Component { constructor() { super(); this.state = {name: ""} } render() { return ( <input value={this.state.name} onChange={e => this.setState({name: e.target.value})} /> ); }}
Conditionals in JSX
分析JSX中的條件渲染
//單條件不建議寫法const sampleComponent = () => { return isTrue ? <p>True!</p> : <none/>};//單條件建議寫法const sampleComponent = () => { return isTrue && <p>True!</p>};//多條件不建議寫法const sampleComponent = () => { return ( <div> {flag && flag2 && !flag3 ? flag4 ? <p>Blah</p> : flag5 ? <p>Meh</p> : <p>Herp</p> : <p>Derp</p> } </div> )};//多條件建議寫法const sampleComponent = () => { return ( <div> { (() => { if (flag && flag2 && !flag3) { if (flag4) { return <p>Blah</p> } else if (flag5) { return <p>Meh</p> } else { return <p>Herp</p> } } else { return <p>Derp</p> } })() } </div> )};
推薦閱讀:
※React + antd 主題色切換/模板切換
※新手學習前端開發加了很多技術群有必要天天看群聊天記錄學習嗎?
※React 是如何重新定義前端開發的
※在 componentWillUnmount 中到底應該清除哪些變數?
※JS/React 開發者的 Atom 終極配置