React文档精读(上篇)

3,615 阅读12分钟
  • pre-notify
  • React中的element是什么
    • 条件渲染
  • element和component
  • 渲染React Element
  • component与pure function
  • 函数式组件和类组件
  • state/状态
    • setState
      • state设置更新时的自动合并机制
      • setState是异步的
  • component和事件
    • 和原生事件的区别
    • this
  • 表单
    • state和受控组件
      • value = {null}
      • 关于select
      • 关于file
    • ref和非受控组件
  • 列表和keys
    • key和重绘
    • li 和
    • key值无法获取
    • 不推荐用索引作为key

pre-notify

previously:JSX,了解一下?

(づ ̄ 3 ̄)づ此文会根据文档的变动而不断更新

React中的element是什么

上回我们解释了什么是JSXJSX是React中对Javascript语法的延伸,它允许我们使用JSX tag来构建一个虚拟DOM作为真实DOM的小型描述文档(它本身是一个Javascript对象)。

其中,一个JSX tag就是一个React Element

条件渲染

我们说过JSX是能参与js流程控制的,So我们能通过if一类的来决定一个React Element什么时候进行渲染什么时候不要。

let element =  (
    <div>
      {isLogin}&&
      <h2>hello {userName}</h2>
    </div>
  );

也支持三元

element和component

上面我们已经知道一个JSX tag就是一个React Element,但一个JSX tag也可以是一个React Component

它长得像这样

let elements = <Component10086 />

这。。。和之前的有撒区别?

首先我们需要注意一个代表Component/组件的JSX tag必须首字母大写,否则它就会把它当做一个普通的element去渲染。

嗯。。。很自然我们会提出一个疑问,那当做普通的element去渲染和当做一个组件去渲染有什么区别呢?

其实,React中的组件就像是Javascript 中的函数,可以接收任意的输入(我们称之为props)并且能够返回React elements,这和我们直接得到React Element的区别在于,我们能在函数内部再对element进行一次封装,做一些条件渲染,设置state什么的。


So,我们光创建一个JSX tag是不够的,我们还需要定义一个对应的函数

function Component10086(props){
  return <h1>Hello Component!</h1>;
}

这个函数有一个pros属性,我们可以像调用函数一样在创建一个JSX tag时传递参数给这个函数,像这样

let elements = <Component10086 prop1='第一个参数' prop2='第二个参数' />

// --- --- ---

//也支持... 来传递多个属性
let data = {
 prop1:'第一个参数'
 ,prop2:'第二个参数'
}
let elements = <Component10086 {...data}/>

上面的prop1prop2两个键值对会被封装成一个props对象作为函数的参数传入

function Component10086(props){
  return (
  <h1 className={props.prop2}/*当做属性渲染*/>
  	Hello Component!
    {props.prop1} //当做children渲染
  </h1>;
  )
}

[important] 注意:在JSX那一篇中我们已经说过JSX是javascript语法的延伸,这意味着一个JSX tag可以被赋值给一个js变量,能参与for和if流程控制,能作为函数传参,能够被当做函数返回值。So组件传参时也能将一个JSX tag做为参数进行传递


渲染React Element

我们已经知道如何构建一个虚拟DOM来描述真实的DOM

let element = <h1 className={class1}>hello React!</h1>

(以上经过babel编译后,我们可以发现它其实是调用React.createELement来创建一个javascript对象来描述真实dom,上回已详细说过不再赘述)

[danger] 引入(import)react库时,React必须是首字母大写,因为编译后我们调用createElement方法时就是首字母大写的React

但怎么将这个虚拟DOM转换成真正的DOM渲染在页面上呢?

这就是我们React中react-dom库所做的事情了。引入这个库后,我们能调用其中的render方法将对应的真实DOM渲染到指定的挂载点上。

import ReactDOM from 'react-dom';
...

ReactDOM.render(
    element
    ,document.geElementById('root')
)

值得注意的是如果这个React Element被重新渲染,react只会重新渲染这个element中发生改变的dom节点。(我们说过一个JSX tag可以有children)。


同样的,我们能使用同样的方式对一个组件进行渲染,

ReactDOM.render(
    <Component10086 prop1='...' />
    ,document.geElementById('root')
)

那么,render是怎样区分渲染的是一个组件还是一个普通的element呢?

我们调用ReactDOM.render去渲染一个JSX tag时,

我们首先会查看这个tag的首字母是否是大写,如果是大写就代表是一个component,那么react就会把它当做一个组件去渲染,

它会首先将JSX tag做为参数传递的属性封装成一个props对象,然后再将这个对象传递给组件函数

组件函数接收到参数对象后会进行一系列处理,最终返回处理完成后的React element

最终,render拿到element转换成真正的DOM元素渲染到页面上。

component与pure function

关于React Component,有一点我们需要注意的是,所有的react组件都必须是一个纯函数。

什么是纯函数呢?

Such functions are called “pure” because they do not attempt to change their inputs, and always return the same result for the same inputs.

React官方给出的即是是,相同的输入会产生相同的输出,并且我们不能在函数内部更改传递过来的props

这意味着,props是静止的,不可更改的,但一些交互性灰常强的UI组件的一些属性是经常会变化的。

那怎么解决这个问题呢?这就是后话了,后面讲到React state就是用来解决这个问题的。

[important] 注意: 如果属性是不参与render()的那么它就不应该被设计成state

函数式组件和类组件

组件有两种形式,函数式的和类形式的,

//function
function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
}

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

ReactDOM.render(<Welcome name='ahhh' age='123'/>,document.getElementById('root'));

此时这两种写法是等价的,但!

class类的形式支持一些函数式组件不支持的功能,比如说state

state/状态

首先state也是prop,但不同于普通的props

普通的props我们可以在JSX tag上传参,react会自动帮我们将这些参数打包后挂载到组件上(this.props)。

state需要我们在组件对象上手动设置,

setState

首先我们在组件中初始化一个组件实例的state

class Clock extends React.Component{
  constructor(props){
    super(props);
    // 在构造函数内部定义初始状态
    this.state = {date:new Date()};
  }

如果这个state需要发生改变,我们需要注意,我们不能直接通过this.state.date = xxx 这样的形式去改变state,这样是不会触发render()进行重绘的。

我们需要通过 this.setState 方法(来自继承的React.Component)来改变我们原有的state

语法: setState(updater[, callback])注意此方法是支持回调的

this.setState({
  date:new Date()
})

另外关于setState有两点需要额外注意

state设置更新时的自动合并机制

我们可能在一个构造函数中初始化多个state键值对

...
this.state = {
    state1:'xx'
    state2:'yy'
}
..

React state更新时的自动合并机制允许我们这样去更新state

this.setState({
    state1:'aaa'
})

可以发现我们只需要在setState中填上我们要更改的部分而不是整个state对象。

setState是异步的

我们可能在一个方法中连续使用多次setState,但由于设置是异步的,我们不能在第二次调用setState方法时拿到第一次调用setState所设置的值,它们的state都是基于最初的state的。

那么这个问题如何解决呢?

其实setState还有第二种形式,使用回调函数而非对象的形式去更新state,像这样

this.setState((prevState,props)=>({counter:prevState.counter + Math.random()}));
this.setState((prevState,props)=>({counter:prevState.counter + props.increment}))

值得一提的是这种写法是 setState(updater[, callback])的语法糖形式,最终仍然会编译成这样执行。

component和事件

和原生事件的区别

  • React事件命名采用的是驼峰式而非原生中的小写形式
  • 原生绑定时传递的是一个字符串,而react则是利用{}
//原生
<button onclick="activateLasers()">
  Activate Lasers
</button>

//React
<button onClick={activateLasers}>
  Activate Lasers
</button>
  • react中的ev对象是经过react封装过的
  • 不支持return false
function ActionLink() {
  function handleClick(e) { //这个event不是原生的,而是自己封装的,故不存在兼容问题
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

this

如果是用类的形式定义的组件,我们需要注意事件函数中this的指向问题。

在react中我们不需要手动使用addEventListener对元素进行事件函数的绑定,只需要在JSX tag上像原生行内式绑定事件函数一样注册事件即可。

但这存在一个问题,react并不会帮我们把回调中的this指向组件实例,甚至这个this也不是指向那个应该绑定的DOM元素

我们是希望这个this指向组件实例的,这样我们能拿到挂载在这个组件实例上的state。而改变回调this指向的方式大概有三种。

  • 通过bind绑定事件处理函数
  • 通过箭头函数包装事件处理函数
  • 通过ES7初始值语法来创建事件处理函数

我们推荐第三种,十分方便

handleClick = ()=>{
    this.setState(...);
}

表单

state和受控组件

正常来说当我们在一个input输入后会立刻得到相应的显示在界面上。

但React允许我们在输入后,先把输入的信息hold住,进行一些处理后再显示在界面上。

这是怎么做到的呢?这就是通过我们之前所说的React state

首先我要让一个input的value等于一个state

 <input type="text" onChange={this.handleChange.bind(this,'username')} value={this.state.username}/>

我们知道一个state在react中只能通过调用setState才会触发render导致重绘UI,这意味着只要我们不立刻调用setState,那么input的值就不会立刻改变。

在上面的示例中我们通过给input绑定一个事件,来对输入进行处理

handleChange = (key,event)=>{
    let val = event.target.value;
    this.setState({[key]:val})
}

这样我们就完成了对表单输入的截获,使其受到了我们的控制,我们将这样的表单元素称之为受控组件。


有一点要注意我们在上栗用到了es6的computed property语法

setState({[key]:val})

其中{[key]:val}就相当于let o={};o[key]=val,这种语法允许我们在设置对象的键名时也能使用变量。

我们之所以要在事件处理函数中传递一个key过去,是因为一张表里可能有很多表单元素,每一个都对应我们组件中的一个state,而这个key就是用来区分他们的。

value = {null}

表示不再是受控组件

关于select

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

不推荐以上,推荐在select里写value

<select value={this.state.value} onChange={this.handleChange}>
    <option value="grapefruit">Grapefruit</option>
    <option value="lime">Lime</option>
    <option value="coconut">Coconut</option>
    <option value="mango">Mango</option>
</select>

多选

<select multiple={true} value={['a', 'b']}>

关于file

input=file 是不会受到react控制的

ref和非受控组件

我们在受控组件中能通过绑定事件处理函数的形式拿到组件中的表单元素。

但是像<input type="file">这种非受控组件,我们如果还想要截获它的值怎么截获到呢?

首先我们不可能像其它表单元素一样在它自个儿身上绑定事件处理函数,因为对于file,只有form表单提交时我们才能拿到file的值,

so我们只能在form上绑定事件处理函数,那怎么拿到form中的file元素呢?

这就需要利用到React ref

<input type="text" ref="username" />

// 处理函数中
let username = this.refs.username.value

以上写法ref=一个字符串已经被废弃

现在推荐这么写

<input type="text" ref={input=>this.username=input} /> //input就是渲染出的真实dom

可以发现,这里的ref里对应的是一个函数,此函数会在当此虚拟DOM转成真实DOM并插入到页面之后立刻调用,参数接收到的就是插入的真实dom

更新:16.3新特のReact.createRef(),ref={input=>this.username=input}仍可用

列表和keys

记得我们说过,JSX tag可以作为函数返回值

So,我们能够这样渲染一个列表

let array = [1,2,3,4,5];
let lists = array.map((item,index)=><li key={index}>{item}</li>);

ReactDOM.render((
  <ul>
    {lists}
  </ul>
), document.getElementById('root'));

注意上栗中我们给每个li都绑定了一个key,这个key是数组中的索引位置,是独一无二的。

key和重绘

如果我们不给li绑定key,React会报一个警告,

Warning: Each child in an array or iterator should have a unique "key" prop.

但其实它已经自动帮我们加上key了。

为什么react渲染列表的时需要一个key呢?

记得我们上面说过react重绘时不会将整个React Element都重绘的吗。

嗯。。。那它是怎么做到的呢?就是利用这个key了,如果没有这个key,它是无法区别element中的tag谁是谁的。

li 和 <ListItem>

有些时候一个li内的内容过于复杂,我们会将其封装成一个组件

这个时候我们推荐把key挂载这个li的组件上,而不是组件内部返回的li上

以下示例出资React文档

unction NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<ListItem key={number.toString()}
          value={number} />

);

key值无法获取

如果我们在组件上传递了一个key值,这个key值并不会被包装进props对象

不推荐用索引作为key

详见 in-depth explanation on the negative impacts of using an index as a key.


参考:

--- ToBeContinue... ---