# 2-3-组件数据

今天我们要一起学习一下 React 组件当中对于数据的处理。

* props
* state
* context

## props

* 传入变量
* 传入函数
* 传入组件
* props.children

props其实就是属性Properties的缩写。

在形式上，props之于JSX就相当于attributes之于HTML。从写法上来看呢，我们为组件传入props就可以像为HTML标签添加属性一样：

**JSX**

```jsx
const SimpleButton = props => 
    {/*<button style={{ color: props.color }}>Submit</button>*/}
    <button className={props.color}>Submit</button>

ReactDOM.render(<SimpleButton color="blue" />, document.getElementById('root'))
```

**HTML**

```markup
<button class="blue">Submit</button>
```

在概念上，props对于组件就相当于JS中参数之于函数。我们可以抽象出这样一个函数来解释：

`Component(props) = View`

组件函数接受props作为参数最后返回视图内容。props比起原生HTML的属性要强大很多。HTML的属性只能传递某些固定的字符，props几乎可以传递所有的内容，包括变量、函数、甚至是组件本身。

**props是只读的**

在React中，props都是自上向下传递，从父组件传入子组件。并且props是只读的，我们不能在组件中直接修改props的内容。也即是说组件只能根据传入的props渲染界面，而不能在其内部对props进行修改。

**props类型检查**

正是因为props的强大，什么类型的内容都可以传递，所以在开发过程中，为了避免错误类型的内容传入，我们可以为props添加类型检查。

**props默认值**

由于props是只读的，我们不能直接为props赋值。React专门准备了一个方法定义props的默认值。

```jsx
import React from 'react'
import PropTypes from 'prop-types'

const Title = props => <h1>{props.title}</h1>

Title.defaultProps = {
  title: 'Wait for parent to pass props.'
}

Title.propTypes = {
  title: PropTypes.string.isRequired
}
```

## state

* 初始化
* setState方法
* 向下传递数据

state如果要翻译的话可以翻译为状态。一个组件可以有状态，就像我们之前介绍过的有状态组件，也可以是整个React应用的状态。

在React中state也是我们进行数据交互的地方，又或者叫做state management状态管理。一个应用需要进行数据交互，比如同服务器之间的交互，同用户输入进行交互。话反过来，从API获取数据，处理用户输入也就是我们需要用到state的时候。

下面我们通过一些例子来说明state的用法以及一些需要特别注意的地方。

先看一个最简单的计数器例子，在新版本的React当中，我们通过类定义组件来声明一个有状态组件，之后在它的构造方法中初始化组件的state，我们可以先赋予它默认值。

之后就可以在组件中通过this.state来访问它，既然是state那么肯定涉及到数据的改变，因此我们还需额外定义一个负责处理state变化的函数，这样的函数中一般都会包含this.setState这个方法。

要注意到，和之前的props一样，初始化state之后，如果我们想改变它，是不可以直接对其赋值的，直接修改state的值没有任何意义，因为这样的操作脱离了React运行的逻辑，不会触发组件的重新渲染。所以需要this.setState这个方法，在改变state的同时，触发React内部的一系列函数，最后在页面上重新渲染出组件。

```jsx
class Counter extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      counter: 0
    }
  }

  addOne() {
    this.setState((prevState) =>({
      counter: prevState.counter + 1
    }))
  }

  render() {
    return (
      <div>
        <p>{ this.state.counter }</p>
        <button
          onClick={() => this.addOne()}>
          Increment
        </button>
      </div>
    )
  }
}
```

根据我们上面学习到的有关props和state的知识，我们再来一起看一个稍微复杂一些的示例：

```jsx
const Title = props => <h1>{props.title}</h1>

const UserInfo = props => <p>{props.firstName + ' ' + props.lastName}</p>

class LikeButton extends React.Component {
  constructor(props) {
    super(props)
  }
  handleClick() {
    this.props.handleClick()
  }
  render() {
    return <button onClick={() => this.handleClick()}>{this.props.mark}</button>
  }
}

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = { mark: '☆' }
  }
  handleButtonClick() {
    const mark = (this.state.mark === '★') ? '☆' : '★'
    this.setState({ mark: mark })
  }
  render() {
    return (
    <div>
        <Title title='Do you like this guy?' />
        <UserInfo firstName='Justin' lastName='Bieber' />
        <LikeButton mark={this.state.mark} handleClick={() => this.handleButtonClick()} />
    </div>
    )
  }
}

const Root = props => <div className='container'>{props.children}</div>

ReactDOM.render(
 <Root>
  <App />
 </Root>,
  document.getElementById('root'))
```

同学们要注意到，在这个例子中，我依旧沿袭了上节课中展示组件和容器组件的概念。只要我们乐意，所有的React组件都可以拥有自己的state，但在实际的生产中，我们写出的90%的都是无状态组件。假如state分散在应用的各个组件当中，有的负责后端数据交互，有的负责处理用户输入，有的负责界面变换逻辑等等，后期维护起来是相当困难的。因此在开发React应用的过程中，我们应该尽量把state集中统一管理，通过props把state的数据传递到需要的组件当中。

比方说下面这个例子，在这个简易的货币汇率转换器中，3个组件，一个输入，两个输出都需要使用到同一个名为money的state，那么我们就需要为3个组件建立一个统一的父组件来管理他们公用的state，通过props将state中的值传递到子组件中，同样也可以将处理state的函数以props的方式传递下去，在子组件中触发父组件修改state的函数，以此来达到更改state重新渲染组件的目的。

```jsx
class CurrencyConvertor extends React.Component {
  constructor() {
    super()
    this.state = {
      money : 100
    }
  }
  handleInputChange(event) {
    this.setState({
      money: event.target.value
    })
  }
  render() {
    return (
    <div>
        <h2>汇率转换</h2>
        <YuanInput money={this.state.money} handleChange={(e) => this.handleInputChange(e)} />
        <MoneyConvertor type='美元' unit='dollar' money={this.state.money} rate={0.1453} />
        <MoneyConvertor type='日元' unit='yen' money={this.state.money} rate={16.1814} />
    </div>
    )
  }
}

class YuanInput extends React.Component {
  constructor(props) {
    super(props)
  }
  handleChange(event) {
    this.props.handleChange(event)
  }
  render() {
    return (
      <p>
        <label>
          人民币
          <input name='yuan' onChange={(e)=>this.handleChange(e)} value={this.props.money} />
        </label>
      </p>
    )
  }
}

const MoneyConvertor = props => (
  <p><label>
    {props.type}
    <input name={props.unit} value={(props.money*props.rate).toFixed(2)} disbled />
  </label></p>
)
```

随着我们开发应用的逐步扩展，它的state会变得越来越庞大复杂，假如分散到各个组件当中，对于日后应用的维护者来说将是一个噩梦。怎么处理怎么储存应用的state非常值得我们深入去思考，由此也就引发了一个问题——状态管理。这也正是我们学习过React之后，在下一个部分的教程中将要介绍的Redux专注解决的问题。

## context

集中统一管理state也会引发另外一个问题，React当中的数据都是自上向下传递的，加入我们的state在最外层的一个组件中管理，但真正使用state中数据的组件可能在N层子组件的嵌套中，那么我们就需要在每层的子组件中都传递props下去，直至使用这个数据的子组件获取到为止，虽然可以让我们更清楚地看到数据流的传递和变化，但写起来真的非常痛苦。

React提供了一个名为context的实验性质的API来解决这一问题。我们直接来看这个对比props和context的例子：

```jsx
const UserInfo = (props, context) => (
  <div className="UserInfo-name">
    {context.lastName + ', ' + props.firstName}
  </div>
)
UserInfo.contextTypes = {
  lastName: React.PropTypes.string
}

const Column = props => <div className='column'><UserInfo firstName={props.firstName} /></div>

const Row = props => <div className='row'><Column firstName={props.firstName} /></div>

const Container = props => <div className='container'><Row firstName={props.firstName} /></div>

class App extends React.Component {
  constructor() {
    super()
    this.state = { firstName: 'Bolun'}
  }
  getChildContext() {
    return {lastName: "Yu"}
  }
  render() {
    return (
      <div className='App'>
        <Container firstName={this.state.firstName} />
      </div>
    )
  }
}
App.childContextTypes = {
  lastName: React.PropTypes.string
}
```

在 Codepen 上试试。

通过props我们需要传递4层才能将数据传递到我们的目标组件中，而利用context的特性，只需要在最外层的应用组件中定义，直接在目标组件中使用即可。我们通过组件中的特殊方法getChildContext来定义context，另外要注意的是，必须用childContextTypes和contextTypes各自声明context的类型，否则我们无法获取context中的数据，只会返回一个空对象。

另外在小项目（比方说 Todo/货币汇率转换器/RSS阅读器 这类数据源单一好理解好控制的这类应用我们就叫它小项目）中，你可以试着把 state 玩溜，在初具规模的项目中我们还有一些更完整的解决方案，在以后的教程当中会介绍给同学们。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://discountry.gitbook.io/learn-react-from-scratch/react/23-zu-jian-shu-ju.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
