02.构建第一个React应用(二)

render-effect.jpeg

github源码地址

承接上文,现在想添加新的功能,使得该应用有更多的交互性。每当点击数字旁边的蓝色向上三角投票按钮(其实这个三角就是用来投票的地方),旁边的数字(票数)就会增加1(票)。这里就涉及到了新的知识点---React的事件。可以参考一下官方文档关于React事件的描述React Event


首先要明确一点:props是不可修改的!!!尽管一个子组件可以获取props,但是它不能修改props。子组件是不能拥有其 props的,在我们的app中,父组件ProductList拥有props,然后传递给子组件Product。React赞成的是一种称之为单项数据流的模式,也就是说,数据是自顶之下传播(propagation)给各个组件。

我们知道父组件是通过props的方式和子组件通信传递数据,那么子组件通过什么方式将数据传递给父组件?
我们可以用props来向下传递一个函数,props可以传递数字,字符串,对象,函数,甚至是组件。我们在ProductList中给每个Product上传递一个函数,每当Product投票按钮,由函数回调的方式将数据从子组件向上传递到父组件。
实战一下:
ProductList.js:

class ProductList extends Component {

  constructor(props) {
    super(props);
    this.state = {
      products: []
    }

    this.handleProductUpVote = this.handleProductUpVote.bind(this)
  }
  handleProductUpVote(productId) {  
  }

const productComponents = sortedProducts.map(product => {
      return (
          <Product
            key={`product-${product.id}`}
            id={product.id}
            title={product.title}
            description={product.description}
            url={product.url}
            votes={product.votes}
            submitterAvatarUrl={product.submitterAvatarUrl}
            productImageUrl={product.productImageUrl}
            onVote={this.handleProductUpVote}/>
        )
    })

这样,ProductList通过props向Product传递一个函数,Product就可以用this.props的方式调用这个函数。
这里:this.handleProductUpVote = this.handleProductUpVote.bind(this)需要给类方法手动绑定this,在JavaScript中,类方法是默认没有绑定this的,如果方法上没有绑定this,那么调用的时候会undefined。当然,也可以使用ES6的箭头函数解决这个问题:

handleProductUpVote = (productId) => {
  // todo...
}

然后,Product组件接受传递的函数:

handleUpVote() {
    this.props.onVote(this.props.id)
  }

在Product的投票按钮上绑定该事件:

  constructor(props) {
    super(props);
    this.state = {}

    this.handleUpVote = this.handleUpVote.bind(this)
  }

  <a onClick={this.handleUpVote}>
    <i className="large caret up icon"></i>
  </a>

当用户点击投票按钮的时候,会触发如下函数调用链:

  1. 用户点击投票按钮
  2. React调用Productd的handleUpVote
  3. handleUpVote调用其propsonVoteonVote方法是父组件ProductList内部的方法,这用通过回调可以将id传递给父组件

接下来要做的,就是如何更新投票的数据。


但是目前的问题是,哪里可以执行这种更新数据的操作?目前为止,我们的app没有一个存储和管理数据的地方。products.json中的数据只是我们的样本数据,而并非app内部的数据。这就引出了一个新的概念:state

使用state
由前面我们可以知道,props是不可变的,且是属于父组件的。而state是由单个组件所属的,父组件可以有自己的state,子组件也可以有自己的statethis.state是组件私有的,而且我们可以使用this.setState()来改变state中的数据。
当一个组件的props或者state发生了变化,那么组件就会自我重新渲染。

我们可以将组件中需要变化的数据认为是有状态的。在ProductList组件中,我们需要改变products的数据(投票的数值数据),所谓我们可将products认为是有状态的(stateful)。然后,ProductList会将这个state数据以props的形式传递给子组件。

现在让我们改造一下ProductList组件:

  1. 将products变成有状态的
  2. 给products添加数据
  3. 以props的形式传递该数据
constructor(props) {
    super(props);
    this.state = {
      products: []
 }

    this.handleProductUpVote = this.handleProductUpVote.bind(this)
 }

  componentDidMount = () => {
    this.setState({
      products: products
    });
  }

 const sortedProducts = this.state.products.sort((a, b) => a.votes - b.votes)
    const productComponents = sortedProducts.map(product => {
      return (
          <Product
            key={`product-${product.id}`}
            id={product.id}
            title={product.title}
            description={product.description}
            url={product.url}
            votes={product.votes}
            submitterAvatarUrl={product.submitterAvatarUrl}
            productImageUrl={product.productImageUrl}
            onVote={this.handleProductUpVote}/>
        )
    })

初始化组件的时候设置“空”(empty)的状态是一个比较好的实践方式。然后等节点挂载好以后设置初始数据。也就是在componentDidMount中设置具体的数据。
这里的componentDidMount是React生命周期函数,这里的是组件挂载以后的生命周期,具体细节后面会阐述。

通过this.setState()设置state
使用this.state = xxx的方式改变state数据有效吗?答案显然是不可以的。因为this.state=xxx仅仅改变了state的数据,而this.setState的工作不仅仅是改变state数据,而且还会重新渲染组件。所以,this.setState主要有两个工作:

  1. 改变state数据
  2. 当state发生变化后重新渲染组件

更新state和不可变性(immutability)
现在,我们想要实现这个功能:当用户点击投票按钮的时候,product中的votes数据会增加。我们刚刚讨论过可以使用this.setState()来修改state。尽管一个组件可以更新其state,但是我们应该将this.state中的对象视为不可变的。如果我们将一个数组或者对象视为不可变的,那么我们不能对其做任何修改。例如:
this.setState({nums: [1, 2, 3]})
如果我们想要在state中的num数组添加一个4,我们可能会使用push()来做:
this.setState({nums: this.state.nums.push(4)})
表面上来看,我们仍然保持着this.state的不可变性。但是实际上,push()已经修改的原有的数组:

console.log(this.state.nums) // [1, 2, 3]
this.state.nums.push(4)
console.log(this.state.nums) // [1, 2, 3, 4]

这是一个不好的实践方式。部分原因是setState()实际上是异步的。所以不能保证React何时将更新state,重新渲染组件,具体的细节以后阐述。

那如何解决这个问题?对于数组,可以使用ES6中的拓展运算符(spread)。对于对象,可以使用ES6中的Object.assign。可以参考:spread运算符Object.assign

实践一下:
在ProductList组件中修改handleProductUpVote()

  handleProductUpVote(productId) {
    let products = [...this.state.products] // 数组的拓展运算符
    products.forEach(product => {
      if (product.id === productId) {
        product.votes = parseInt(product.votes) + 1
      }
    })

    this.setState({
      products: products
    });
  }

forEach参考

这样我们的voting-app就可以实现投票的功能了。而且投票数发生变化以后,会自动排序。具体可以参考github上的代码,并且运行一下。

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

推荐阅读更多精彩内容