React基礎使用部分

init

ReactDOM.render(
  <h1>hello, world!</h1>
  document.getElementById('root')
)

JSX

JSXJavascript的語法擴展,建議在React中配合使用JSX,可以描述UI交互,并且具有Javascript的全部功能。JSX中放置的大括號{}會被當做有效的Javascript表達式執行。Babel會將JSX轉譯成 React.createElement函數調用。以下兩種示例代碼完全等效

cont element = (
  <h1 className="greeting">
    Hello. word 
  </h1>
)
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'hello. world'
)

React.createElement函數會預先執行一些檢查,以幫助編寫無錯代碼,實際上創建了一個被稱為 React元素的對象,React通過讀取這些對象,使用他們來構建DOM并保持數據更新。

const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'hello. world'
  }
}

元素渲染

React元素是不可變對象,被創建后無法更改它的子元素或屬性。更新UI的方式是創建一個全新的元素,將其傳入ReactDOM.render()React DOM會將元素和它的子元素間的狀態進行比較,只會進行必要的更新使得DOM達到預期的狀態。

function tick() {
  const element = (
    <div>
      <h1>hello. world</h1>
      <h2>It is { new Date().toLocaleTimeString() }<h2>
    </div>
  )
  ReactDOM.render(element, document.getElementBtId('root'))
}
setInterval(tick, 1000)

組件 & Props

組件允許將 UI 拆分為獨立可復用的代碼片段,接受任意的入參 props,返回用于展示頁面元素內容的 React元素。

函數組件接收 props 對象,返回 React元素。

function Welcome(props) {
 return <h1>hello {props.name}</h1>
}

也可以使用 ES6class定義組件

class Welcome extends React.Component {
  render() {
    return <h1>hello {this.props.name}</h1>
  }
}

渲染組件

React元素為用戶自定義組件時,會將 JSX接收的屬性和子組件轉換為單個props對象傳遞給組件。下面的部分會被渲染成 Hello Sara

function Welcome(props) {
  return <h1>hello . {props.name}</h1>
}

const element = <Welcome name="Sara"/>
ReactDOM.render(
  element,
  document.getElementById('root')
)

組件名稱必須以大寫字母開頭,React會將以小寫字母開頭的組件視為原生 DOM標簽。這個例子中發生的流程

  • 調用 ReactDOM.render()函數,并傳入<Welcome name="Sara"/>作為參數

  • React調用 Welcome組件,并將{name: 'Sara'}作為 props傳入

  • Welcome組件將<h1>Hello, Sara</h1>元素作為返回值

  • React DOMDOM高效更新為<h1>Hello, Sara<h1>

每個新的React應用程序的頂層組件都是App組件,如果將React集成到現有的應用程序中,需要使用一些小的功能性組件,自下而上地將這類組件逐步應用到視圖中的每一處。

function Welcome(props) {
  return <h1>hello . {props.name}</h1>
}
  
function App() {
  return (
    <div>
      <Welcome name="Sara"/>
      <Welcome name="Cahal"/>
      <Welcome name="Edite"/>
    </div>
  )
}
ReactDOM.render(
  <App />,
  document.getElementById('root')
)

所有 React組件必須像純函數(不會更改函數入參)一樣保護 props 不被更改。state允許React組件隨用戶操作、網絡相應或者其他變化而動態更改輸出內容。

State 與生命周期

stateprops類似,props是私有的,并且完全受控于當前組件。將函數組件轉換成 class組件:

class Clock extends React.Component {
  constructor(props) {
    super(props) //將props傳遞到父類的構造函數中, class組件應該始終使用props參數來調用父類的構造函數
    this.state = {date: new Date()}
  }
  componentDidMount() {//componentDidMount方法會在組件已經被渲染到DOM中后運行
    this.timerId = setInterval(() => this.tick(), 100)
  }
  componentWillUnmount() {
    clearInterval(this.timerId)
  }
  tick() {
    this.setState({ //使用 this.setState()來時刻更新組件
      date: new date()
    })
  }
  render() {
    return (
      <div>
        <h1>hello . world</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    )
  }
}
ReactDOM.render(
  <Clock/>,
  document.getElementById('root')
)

每次組件更新時render方法都會被調用,但只要在相同的DOM節點中渲染<Clock/>,就僅有一個Clock組件的class實例被創建使用,使得我們可以使用state或生命周期方法的很多特性。

關于state使用的注意事項

  • 構造函數是唯一可以給this.state賦值的地方,其他情況下不要直接修改state,要通過setState()

    this.setState({comment: 'hello'})
    
  • state的更新可能是異步的。出于性能考慮,React可能會把多個setState()調用合并成一個調用。this.propsthis.state可能是異步更新的,不要依賴他們的值更新下一個狀態。解決方式是讓setState接收一個函數而不是對象。

    this.setState((state, props) => ({
      m: state.a + props.b
    }))
    
  • 當調用 setState()時,React會把提供的對象合并到當前的state

  • 數據是向下流動的,除了擁有并設置state的組件,其他組件無法訪問。組件可以選擇把它的state作為props向下傳遞到它的子組件中。

    function FormattedDate(props) {
      return <h2>It is { props.date.toLocaleTimeString() }</h2>
    }
    <FormattedDate date={this.state.date}></FormattedDate>
    

事件處理

React元素的事件處理和DOM元素的很相似,但有幾點不同

  • React的事件命名采用小駝峰式,不是純小寫

  • 使用JSX語法時需要傳入一個函數作為事件處理函數,不是字符串

    <button onClick={activateLasers}> Active Lasers</button>
    
  • 不能通過返回false的形式阻止事件的默認行為,必須使用顯示的preventDefault

    function ActionLink() {
      function handleClick(e) {
        e.preventDefault() // e是一個合成事件,react 根據 w3c 規范來定義這些合成事件,不需要擔心跨瀏覽器的兼容問題
        console.log('The link was clicked')
      }
      return (
        <a href="#" onClick={handleClick}>Click me</a>
      )
    }
    
  • React中,不需要使用addEventListener為已創建的DOM元素添加監聽器,只需要在元素初始渲染的時候添加監聽器即可

    // 渲染一個讓用戶切換開關狀態的按鈕
    class Toggle extends React.Component {
      constructor(props) {
        super(props)
        this.state = { isToggleOn: true }
        this.handleClick = this.handleClick.bind(this) // 為了在回調中綁定 this,這個綁定是必不可少的
      }
      handleClick() {
        this.setState(state => ({
          isToggleOn: !state.isToggleOn
        }))
      }
      render() {
        return (
          <button onClick={this.handleClick}>
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        )
      }
    }
    
  • JSX函數中的this需要正常使用的情況下,需要使用bind進行綁定

    class Toggle extends React.Component {
      constructor(props) {
        super(props)
        this.state = { isToggleOn: true}
        this.handleClick = this.handleClick.bind(this) //為了在回調中使用this
      }
      
      handleClick() {
        this.setState(state => ({
          isToggleOn: !state.isToggleOn
        }))
      }
      // class Fields 語法
      handleClick = () => {
        console.log('this is:', this)
      }
      
      render() {
        return (
          <button onClick={this.handleClick}>
          <!-->
          或者在回調中使用箭頭函數
          <button onClick={() => this.handleClick()}>
          <-->
            {this.state.isToggleOn ? 'ON' : 'OFF'}
          </button>
        )
      }
    }
    ReactDOM.render(
      <Toggle/>,
      document.getElementById('root')
    )
    
  • 事件的參數傳遞

    <button onClick={(e) => this.deleteRow(id, e)}>Detelet Row</button>
    <button onClick={this.deleteRow(this, id)}>Detelet Row</button>
    

條件渲染

  • if

    function UserGreeting(props) {
      return <h1>Welcome back</h1>
    }
    function GuestGreeting(props) {
      return <h1>Please Sign up</h1>
    }
    // 創建 Greeting 組件,根據用戶是否登錄決定顯示哪一個組件
    function Greeting(props) {
      const isLoggedIn = props.isLoggedIn
      if(isLoggedIn) {
        return <UserGreeting/>
      } 
      return <GuestGreeting/>
    }
    
    ReactDOM.render(
      <Greeting isLoggedIn={false}/>,
      document.getElementById('root')
    )
    
  • 與 &&

    function Mailbox(props) {
      const unreadMessage = props.unreadMessage
      return (
        <div>
          <h1>hello</h1>
          {unreadMessage.length > 0 &&
            <h2>You hava {unreadMessage.length} unread message/</h2>
          }
        </div>
      )
    }
    const message = ['a', 'b', 'c']
    ReactDOM.render(
      <Mailbox unreadMessages={message}/>,
      document.getElementById('root')
    )
    
  • 三目 ? :

    render() {
      const isLoggedIn = this.state.isLoggedIn
      return (
        <div>
          The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
        </div>
      )
    }
    
  • 阻止條件渲染

    在組件已經被其他組件渲染的情況下,隱藏組件,設置在組件的render方法中返回null

    function WarningBanner(props) {
      if(!props.warn) return null
      return (
        <div classNmae='warning'>Warning</div>
      )
    }
    

列表 & key

  • 列表渲染

    function NumberList(props) {
      const numbers = props.numbers
      const listItems = numbers.map((number) => 
        <li key={number.toString()}>
          {number}
        </li>
      )
      return (
        <ul>{listItems}</ul>
      )
    }
    const numbers = [1, 2, 3, 4, 5]
    ReactDOM.render(
      <NumberList number={number}/>,
      document.getElementById('root')
    )
    

key幫助React識別元素的添加和刪除,應該給數組中的每一個元賦予一個確定的標識。一個元素的key最好是這個元素在列表中獨一無二的字符串,通常使用數據的id作為元素的key。如果選擇不顯示指定key值,react將默認使用索引作為列表項目的key值。key只需要在兄弟節點之間唯一,不需要全局唯一。在map方法中的元素需要設置key屬性。

function ListItem(props) {
  return <li>{props.value}</li>
}
function NumberList(props) {
  const numbers = props.numbers
  const listItens = numbers.map((number) => 
    <ListItem key={number.toString()} value={number}/>
  )
  return (
    <ul>
      {listItems}
    </ul>                              
  )                               
}
const numbers = [1, 2, 3, 4, 5]
ReactDOM.render(
  <NumberList number={numbers}/>,
  document.getElementById('root')                                
)                                                              

ket會傳遞信息給React,但不會傳遞給組件。如果組件中需要使用key屬性的值,請用其他屬性名顯示傳遞這個值。

const content = posts.map((post) => 
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
)
// Post組件可以讀出 props.id,不能讀出 props.key

表單

html中,表單元素 <input><textarea><select>通常自己維護state,并且根據用戶輸入進行更新。在React中,可變狀態 mutable state通常保存在組件的state屬性中,只能通過setState更新。Reactstate作為作為表單元素的唯一數據源,渲染表單的React組件控制著用戶輸入過程中表單發生的操作,這種表單元素叫做受控組件

class NameForm 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('提交的名字:' + this.state.value)
    event.preventDefault()
  }
  
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type='text' value={this.state.value} onChange={this.handleChange}></input>
        </label>
        <input type='submit' value='提交'/>
      </form>
    )
  }
}

由于在表單元素上設置了value屬性,因此顯示的值將始終為this.state.value,使得Reactstate成為唯一數據源。由于handleChange在每次更新時都會執行并更新Reactstate,顯示的值將隨著用戶輸入而更新。

一些需要注意的處理場景:

  • 文件input標簽。在HTML中,<input type='file'>允許用戶從存儲設備中選擇一個或多個文件,將其上傳到服務器,此時為受控組件。

    <input type='file'> <!--value只讀,是React中的一個非受控組件-->
    
  • 受控組件輸入空值

    ReactDOM.render(<input value='hi'/>, mountNode)
    setTimeout(function(){
      ReactDOM.render(<input value={null}/>, mountNode)
    }, 1000)
    

狀態提升

多個組件需要反映相同的變化數據,將共享狀態提升到最近的共同父組件中。實現一個輸入的溫度轉換功能(攝氏度與華氏度)

class Temperature extends React.Component {
  constructor(props) {
    super(props)
    this.handleChange = this.handleChange.bind(this)
  }
  handleChange(e) {
    this.props.onTemperatureChange(e.target.value)
    // 移除組件自身的 state,通過獲取 props 獲取溫度數據,當想要響應數據改變時,需要調用 父組件提供的 this.props.onTemperatureChange(), 不再使用 this.setState()
  }
  render() {
    const temperature = this.props.temperature
    const scale = this.props.scale
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNmaes[scale]}</legend>
        <input value={temperature}
               onChange={this.handleChange}/>
      </fieldset>
    )
  }
}

針對整體的Calculator組件

class Calculator extends React.Component {
  constructor(props) {
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this)
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this)
    this.state = {temperature: '', scale: 'c'}
  }
  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature})
  }
  handleFahrenheitChange(temperature) {
    this.steState({scale: 'f', temperature})
  }
  render() {
    const scale = this.state.scale
    const temperature = this.state.temperature
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature
    return (
      <div>
        <Temperatureinput 
          scale='c'
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <Temperatureinput
          scale='f'
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange}/>
          />
      </div>
    )
  }
}

React中,任何可變數據應當只有一個唯一數據源,state是首先添加到需要渲染數組的組件中,如果其他組件也需要這個state,可以將他提升到這些組件的最近共同父組件中,依靠自上而下的數據流,而不是嘗試在不同組件間同步state。提升state的方式比雙向綁定方式需要編寫更多代碼,但是可以使得排查和隔離bug所需的工作量變小。

組合 & 繼承

  • 使用 children prop 將子組件傳遞到渲染結果中

    function SplitPane(props) {
      return (
        <div className='SplitPane'>
          <div className='SplitPane-left'>
            {pros.left}
          </div>
          <div className='SplitPane-right'>
            {pros.right}
          </div>
        </div>
      )
    }
    
    function App() {
      return (
        <SplitPane
          left={
            <Contacts/>
          }/>
        <SplitPane
          right={
            <Chat/>
          }/>
      )
    }
    
  • FaceBook的各個應用場景下,沒有發現需要使用繼承來構建組件層次的情況。props和組合提供了定制組件的靈活方式,組件可以接受任意props,包括基本數據類型,React元素及函數。如果想在組件間復用非UI的功能,可以將其提取為單獨的javascript模塊,組件可以直接import,無需通過extend繼承

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。