> For the complete documentation index, see [llms.txt](https://discountry.gitbook.io/learn-react-from-scratch/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://discountry.gitbook.io/learn-react-from-scratch/react/23-zu-jian-shu-ju.md).

# 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 玩溜，在初具规模的项目中我们还有一些更完整的解决方案，在以后的教程当中会介绍给同学们。
