元素與元件Element & Component
#函數定義與類別定義元件Functional & Class
#展示與容器元件Presentational & Container
有狀態與無狀態元件Stateful & Stateless
元素
元素是建構React應用的最小單位。元素所描述的也就是你在瀏覽器中所能看到的東西。根據我們在上課時講到的內容,我們在寫React程式碼時一般會用JSX來描述React元素。 在作用上,我們可以把React元素理解為DOM元素;但實際上,React元素只是JS當中普通的物件。 React內部實作了一套叫做React DOM的東西,或者我們稱為Virtual DOM也就是虛擬DOM.透過一個樹狀結構的JS物件來模擬DOM樹。 說到這裡我們可以稍微講一下,React為什麼會有這一層虛擬DOM呢?在課程介紹中我們曾經提到過,React很快、很輕。它之所以快就是因為這套虛擬DOM的存在,React內部也實現了一個低複雜度高效率的Diff演算法,不同於以往框架,例如angular使用的髒檢查。在應用的資料改變之後,React會盡力少比較,然後根據虛擬DOM只改變真實DOM中需要被改變的部分。 React也藉此實現了它的高效率,高效能。 當然這不是虛擬DOM唯一的意義,透過這一層單獨抽象的邏輯讓React有了無限的可能,就比如react native的實現,可以讓你只掌握JS的知識也能在其他平台系統上開發應用,而不只是寫網頁,甚至是之後會出現的React VR或React物聯網等等別的實作。 話說回來,元素也就是React DOM之中描述UI介面的最小單位。剛才我們說到了,元素其實就是普通的JS物件。不過我們用JSX來描述React元素在理解上可能有些困難,事實上,我們也可以不使用JSX來描述:const element = <h1>Hello, world</h1>; // 用JSX描述就相当于是调用React的方法创建了一个对象 const element = React.createElement('h1', null, 'Hello, world');
元件
要注意到,在React當中元素和元件是兩個不同的概念,之所以在前面講了這麼多,就是擔心大家不小心會混淆這兩個概念。首先我們要先明確的是,元件是建構在元素的基礎上的。 React官方對元件的定義呢,是指在UI介面中,可以被獨立劃分的、可重複使用的、獨立的模組。其實就類似JS當中對function函數的定義,它一般會接收一個名為props的輸入,然後回傳對應的React元素,再交給ReactDOM,最後渲染到螢幕上。 函數定義與類別定義元件 Functional & Class新版本的React裡提供了兩種定義元件的方法。當然之前的React.createClass也可以繼續用,不過我們在這裡先不納入我們討論的範圍。 第一種函數定義元件,非常簡單啦,我們只需要定義一個接收props傳值,返回React元素的方法即可:function Title(props) { return <h1>Hello, {props.name}</h1>; }甚至使用ES6的箭頭函數簡寫之後可以變成這樣:
const Title = props => <h1>Hello, {props.name}</h1>第二種是類別定義元件,也就是使用ES6中新引入的類別的概念來定義React元件:
class Title extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }之後呢,根據我們在上一堂課中了解到的,組件在定義好之後,可以透過JSX描述的方式被引用,組件之間也可以相互嵌套和組合。 展示與容器元件Presentational & Container接下來我們還會介紹一些更深入的關於元件概念,現在聽起來可能會比較抽象枯燥,不過接下來要介紹的這幾個概念在之後的課程中都是會被應用到的,同學們也可以根據自己的實際情況,在學習完後續的課程之後,再返回來聽聽看,相信一定會對你理解React有所幫助。 首先是最重要的一組概念:展示元件與容器元件。同樣,在課程介紹中我們提到的,React並不是傳統的MVVM框架,它只是在V層,視圖層上下功夫。同學應該對MVVM或MVC都有所了解,那麼既然我們的框架現在只有V層的話,在實際開發中應該如何處理資料與視圖的關係呢? 為了解決React只有V層的這個問題,更好地區分我們的程式碼邏輯,展示元件與容器元件這一對概念就被引入了。這同樣也是我們在開發React應用程式時的最佳實務。 我們還是先看一個具體的例子來解釋這兩個概念:
class CommentList extends React.Component { constructor(props) { super(props) this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}) }.bind(this) }) } renderComment({body, author}) { return <li>{body}—{author}</li> } render() { return <ul> {this.state.comments.map(this.renderComment)} </ul> } }
这是一个回复列表组件,乍看上去很正常也很合理。但实际上在开发React应用时,我们应该避免写出这样的组件,因为这类组件担负的功能太多了。它只是一个单一的组件,但需要同时负责初始化state,通过ajax获取服务器数据,渲染列表内容,在实际应用中,可能还会有更多的功能依赖。这样,在后续维护的时候,不管是我们要修改服务器数据交互还是列表样式内容,都需要去修改同一个组件,逻辑严重耦合,多个功能在同一个组件中维护也不利于团队协作。
通过应用展示组件与容器组件的概念,我们可以把上述的单一组件重构为一个展示回复列表组件和回复列表容器:
// 展示组件 class CommentList extends React.Component { constructor(props) { super(props); } renderComment({body, author}) { return <li>{body}—{author}</li>; } render() { return <ul> {this.props.comments.map(this.renderComment)} </ul>; } } // 容器组件 class CommentListContainer extends React.Component { constructor() { super() this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}) }.bind(this) }) } render() { return <CommentList comments={this.state.comments} /> } }
像这样回复列表如何展示与如何获取回复数据的逻辑就被分离到两个组件当中了。我们再来明确一下展示组件和容器组件的概念:
展示组件
主要负责组件内容如何展示
从props接收父组件传递来的数据
大多数情况可以通过函数定义组件声明
容器组件
主要关注组件数据如何交互
拥有自身的state,从服务器获取数据,或与redux等其他数据处理模块协作
需要通过类定义组件声明,并包含生命周期函数和其他附加方法
那么这样写具体有什么好处呢?
解耦了界面和数据的逻辑
更好的可复用性,比如同一个回复列表展示组件可以套用不同数据源的容器组件
利于团队协作,一个人负责界面结构,一个人负责数据交互
有状态组件
意思是这个组件能够获取储存改变应用或组件本身的状态数据,在React当中也就是state,一些比较明显的特征是我们可以在这样的组件当中看到对this.state的初始化,或this.setState方法的调用等等。
无状态组件
这样的组件一般只接收来自其他组件的数据。一般这样的组件中只能看到对this.props的调用,通常可以用函数定义组件的方式声明。它本身不会掌握应用的状态数据,即使触发事件,也是通过事件处理函数传递到其他有状态组件当中再对state进行操作。
我们还是来看具体的例子比较能清楚地说明问题,与此同时,我们已经介绍了三组概念,为了防止混淆,我这里特意使用了两个展示组件来做示例,其中一个是有状态组件,另一个是无状态组件,也是为了证明,并不是所有的展示组件都是无状态组件,所有的容器组件都是有状态组件。再次强调一下,这是两组不同的概念,以及对组件不同角度的划分方式。
//Stateful Component class StatefulLink extends React.Component { constructor(props) { super(props) this.state = { active: false } } handleClick() { this.setState({ active: !this.state.active }) } render() { return <a style={{ color: this.state.active ? 'red' : 'black' }} onClick={this.handleClick.bind(this)} > Stateful Link </a> } } // Stateless Component class StatelessLink extends React.Component { constructor(props) { super(props) } handleClick() { this.props.handleClick(this.props.router) } render() { const active = this.props.activeRouter === this.props.router return ( <li> <a style={{ color: active ? 'red' : 'black' }} onClick={this.handleClick.bind(this)} > Stateless Link </a> </li> ) } } class Nav extends React.Component { constructor() { super() this.state={activeRouter: 'home'} } handleSwitch(router) { this.setState({activeRouter: router}) } render() { return ( <ul> <StatelessLink activeRouter={this.state.activeRouter} router='home' handleClick={this.handleSwitch.bind(this)} /> <StatelessLink activeRouter={this.state.activeRouter} router='blog' handleClick={this.handleSwitch.bind(this)} /> <StatelessLink activeRouter={this.state.activeRouter} router='about' handleClick={this.handleSwitch.bind(this)} /> </ul> ) } }
上述的例子可能稍有些复杂,事实上,在React的实际开发当中,我们编写的组件大部分都是无状态组件。毕竟React的主要作用是编写用户界面。再加上ES6的新特性,绝大多数的无状态组件都可以通过箭头函数简写成类似下面这样:
/* function SimpleButton(props) { return <button>{props.text}</button> } */ const SimpleButton = props => <button>{props.text}</button>
受控组件
一般涉及到表单元素时我们才会使用这种分类方法,在后面一节课程表单及事件处理中我们还会再次谈论到这个话题。受控组件的值由props或state传入,用户在元素上交互或输入内容会引起应用state的改变。在state改变之后重新渲染组件,我们才能在页面中看到元素中值的变化,假如组件没有绑定事件处理函数改变state,用户的输入是不会起到任何效果的,这也就是“受控”的含义所在。
非受控组件
类似于传统的DOM表单控件,用户输入不会直接引起应用state的变化,我们也不会直接为非受控组件传入值。想要获取非受控组件,我们需要使用一个特殊的ref属性,同样也可以使用defaultValue属性来为其指定一次性的默认值。
我们还是来看具体的例子:
class ControlledInput extends React.Component { constructor() { super() this.state = {value: 'Please type here...'} } handleChange(event) { console.log('Controlled change:',event.target.value) this.setState({value: event.target.value}) } render() { return ( <label> Controlled Component: <input type="text" value={this.state.value} onChange={(e) => this.handleChange(e)} /> </label> ); } } class UncontrolledInput extends React.Component { constructor() { super(); } handleChange() { console.log('Uncontrolled change:',this.input.value); } render() { return ( <label> Uncontrolled Component: <input type="text" defaultValue='Please type here...' ref={(input) => this.input = input} onChange={() =>this.handleChange()} /> </label> ); } }
通常情况下,React当中所有的表单控件都需要是受控组件。但正如我们对受控组件的定义,想让受控组件正常工作,每一个受控组件我们都需要为其编写事件处理函数,有的时候确实会很烦人,比方说一个注册表单你需要写出所有验证姓名电话邮箱验证码的逻辑,当然也有一些小技巧可以让同一个事件处理函数应用在多个表单组件上,但生产开发中并没有多大实际意义。更有可能我们是在对已有的项目进行重构,除了React之外还有一些别的库需要和表单交互,这时候使用非受控组件可能会更方便一些。
前面我们已经提到了,React当中的组件是通过嵌套或组合的方式实现组件代码复用的。通过props传值和组合使用组件几乎可以满足所有场景下的需求。这样也更符合组件化的理念,就好像使用互相嵌套的DOM元素一样使用React的组件,并不需要引入继承的概念。
当然也不是说我们的代码不能这么写,来看下面这个例子:
// Inheritance class InheritedButton extends React.Component { constructor() { super() this.state = { color: 'red' } } render() { return ( <button style={{backgroundColor: this.state.color}} class='react-button'>Inherited Button</button> ) } } class BlueButton extends InheritedButton { constructor() { super() this.state = { color: '#0078e7' } } } // Composition const CompositedButton = props => <button style={{backgroundColor:props.color}}>Composited Button</button> const YellowButton = () => <CompositedButton color='#ffeb3b' />
但继承的写法并不符合React的理念。在React当中props其实是非常强大的,props几乎可以传入任何东西,变量、函数、甚至是组件本身:
function SplitPane(props) { return ( <p className="SplitPane"> <p className="SplitPane-left"> {props.left} </p> <p className="SplitPane-right"> {props.right} </p> </p> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
以上是React中元件的寫法有哪些的詳細內容。更多資訊請關注PHP中文網其他相關文章!