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继承

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335