React 入门

Learning from React.js 小书

安装react.js全家桶

安装 React.js
React.js 单独使用基本上是不可能的事情。不要指望着类似于 jQuery 下载放到 <head /> 标签就开始使用。使用 React.js 不管在开发阶段生产阶段都需要一堆工具和库辅助,编译阶段你需要借助 Babel;需要 Redux 等第三方的状态管理工具来组织代码;如果你要写单页面应用那么你需要 React-router。这就是所谓的“React.js全家桶”。

使用JSX描述UI信息

你会发现,HTML 的信息和 JavaScript 所包含的结构和信息其实是一样的,我们可以用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息。但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。
于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class Header extends Component {
  render () {
    return (
      <div>
        <h1 className='title'>React 小书</h1>
      </div>
    )
  }
}
ReactDOM.render(
  <Header />,
  document.getElementById('root')
)

所以可以总结一下从 JSX 到页面到底经过了什么样的过程:
JSX 描述 React.js 组件图片

要记住几个点:
1. JSX 是 JavaScript 语言的一种语法扩展,长得像 HTML,但并不是 HTML。
2. React.js 可以用 JSX 来描述你的组件长什么样的。
3. JSX 在编译的时候会变成相应的 JavaScript 对象描述。
4. react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。

组件的Render方法

React.js 中一切皆组件,用 React.js 写的其实就是 React.js 组件。在编写组件的时候,一般都要继承React.jsComponent,eg:

import React, { Component } from 'react';

一个组件类必须要实现一个render方法,这个render方法必须要返回一个JSX元素。需要注意的是,只能返回一个元素,不能返回并列的多个JSX元素,如以下是错误的:

...
render () {
  return (
    <div>第一个</div>
    <div>第二个</div>
  )
}
...

必须要用一个外层<div></div>包裹

表达式插入: 如{word}。这里可以放变量,表达式计算,函数执行等。

...
render () {
  const word = 'is good'
  return (
    <div>
      <h1>React 小书 {word}</h1>
    </div>
  )
}
...

注意,直接使用 classReact.js 的元素上添加类名如 <div class=“xxx”>这种方式是不合法的。因为 classJavaScript 的关键字,所以 React.js 中定义了一种新的方式:className 来帮助我们给元素添加类名。

  • JSX 元素变量
    同样的,如果你能理解 JSX 元素就是 JavaScript 对象。那么你就可以联想到,JSX 元素其实可以像 JavaScript 对象那样自由地赋值给变量,或者作为函数参数传递、或者作为函数的返回值。
...
render () {
  const isGoodWord = true
  const goodWord = <strong> is good</strong>
  const badWord = <span> is not good</span>
  return (
    <div>
      <h1>
        React 小书
        {isGoodWord ? goodWord : badWord}
      </h1>
    </div>
  )
}
...

组件的组合、嵌套和组件树

//组件的嵌套
class Title extends Component {
  render () {
    return (
      <h1>React 小书</h1>
    )
  }
}

class Header extends Component {
  render () {
    return (
      <div>
        <Title />
      </div>
    )
  }
}

这样的可复用性非常强。这里要注意的是,自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头。

事件监听

没有经过特殊处理的话,这些 on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。也就是说,<Header onClick={…} /> 这样的写法不会有什么效果的。这一点要注意,但是有办法可以做到这样的绑定,以后我们会提及。现在只要记住一点就可以了:这些 on* 的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上。

案例:(添加绑定)

import React, { Component } from 'react';

class LikeButton extends Component{
    constructor () {
        super()
        this.state = { liked: false }
    }
    handleClickOnLikeButton() {
        this.setState({liked: !this.state.liked})
    }
    render(){
        return (
            <button onClick={this.handleClickOnLikeButton.bind(this)}>
                {this.state.liked ? '取消' : '点赞'} 
            </button>
        );
    };
}
export  default  LikeButton;

这样的结果就是,用户每次点击,handleClickOnLikeButton 都会调用改变组件状态然后调用 setStatesetState 会调用 renderrender 方法会根据 state 的不同重新构建不同的 DOM 元素。

也就是说,你只要调用 setState,组件就会重新渲染。我们顺利地消除了手动的 DOM 操作。

注意:(后面有提)
this.handleClickOnLikeButton.bind(this)要进行控件的绑定bind(this)

  • event对象
    和普通浏览器一样,事件监听函数会被自动传入一个 event 对象,这个对象和普通的浏览器 event 对象所包含的方法和属性都基本一致。
//我们来尝试一下,当用户点击`h1`的时候,把`h1`的`innerHTML`打印出来
class Title extends Component {
  handleClickOnTitle (e) {
    console.log(e.target.innerHTML)
  }
  render () {
    return (
      <h1 onClick={this.handleClickOnTitle}>React 小书</h1>
    )
  }
}
  • 关于事件中的this
    一般在某个类的实例方法里面的 this 指的是这个实例本身。但是你在上面的 handleClickOnTitle 中把 this 打印出来,你会看到 thisnull 或者 undefined
...
  handleClickOnTitle (e) {
    console.log(this) // => null or undefined
  }
...

这是因为 React.js 调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClickOnTitle),而是直接通过函数调用 (handleClickOnTitle),所以事件监听函数内并不能通过 this 获取到实例。

如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法 bind 到当前实例上再传入给 React.js。
onClick={this.handleClickOnTitle.bind(this)}

也可以在 bind 的时候给事件监听函数传入一些参数:

class Title extends Component {
  handleClickOnTitle (word, e) {
    console.log(this, word)
  }
  render () {
    return (
      <h1 onClick={this.handleClickOnTitle.bind(this, 'Hello')}>React 小书</h1>
    )
  }
}

总结

  • 为 React 的组件添加事件监听是很简单的事情,你只需要使用 React.js 提供了一系列的 on* 方法即可。

  • React.js 会给每个事件监听传入一个 event 对象,这个对象提供的功能和浏览器提供的功能一致,而且它是兼容所有浏览器的。

  • React.js 的事件监听方法需要手动 bind 到当前实例,这种模式在 React.js 中非常常用。

组件的state和setState

案例:

class LikeButton extends Component {
  constructor () {
    super()
    this.state = { isLiked: false }
  }
  handleClickOnLikeButton () {
    this.setState({
      isLiked: !this.state.isLiked
    })
  }
  render () {
    return (
      <button onClick={this.handleClickOnLikeButton.bind(this)}>
        {this.state.isLiked ? '取消' : '点赞'} 
      </button>
    )
  }
}

先在构造constructor()的时候初始化this.state, 然后在onClick()函数中定义this.setState()函数。setState()函数调用完只有,会自动进行重新渲染。

  • setState接受函数参数
    这里需要注意的是,当我们调用setState的时候,React不会马上修改state,而是把state反倒一个更新队列里面,稍后才会把新的状态提取出来,然后再触发组件更新。所以,以下这种后续运算将无效:
...
  handleClickOnLikeButton () {
    this.setState({ count: 0 }) // => this.state.count 还是 undefined
    this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
    this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
  }
...

这里就自然地引出了 setState 的第二种使用方式,可以接受一个函数作为参数。React.js 会把上一个 setState 的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state 的对象:

...
  handleClickOnLikeButton () {
    this.setState((prevState) => {
      return { count: 0 }
    })
    this.setState((prevState) => {
      return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
    })
    this.setState((prevState) => {
      return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
    })
    // 最后的结果是 this.state.count 为 3
  }
...

一道练习题“不能摸的狗(二)”

class Dog extends Component {
  constructor(){
    super()
    this.state = {
      isRunning: false,
      isBarking: false
    }
  }

  // 随机数(秒数)
  getRandomArbitrary(min, max) {
  return Math.floor(Math.random() * (max - min)) + min;
  }

  run(){
    this.setState( {
      isRunning: true,
    })
    setTimeout(() => this.setState({ isRunning: false }), this.getRandomArbitrary(2000,5000)) //2~5秒, 20~50毫秒眼花+_+
  }

  bark(){
    this.setState( {
      isBarking: true,
    })
    setTimeout(() => this.setState({ isBarking: false }), this.getRandomArbitrary(2000,5000)) //2~5秒, 20~50毫秒眼花+_+
  }

  handleDog(){
    this.run()
    this.bark()
  }

  render(){
    return (
      <div>
        <button onClick={this.handleDog.bind(this)}>HandleDog</button>
        <h5>{this.state.isRunning ? 'Dog is Running' : 'Dog is stoped'}</h5>
        <h5>{this.state.isBarking ? 'Dog is Barking' : 'Dog is stop barking'}</h5>
      </div>
    )
  }
}

配置组件的props

定义:

const likedText = this.props.likedText || '取消'
const unlikedText = this.props.unlikedText || '点赞'

可以用以下方式从JSX中传参数过去:

<LikeButton likedText='已赞' unlikedText='赞' />

也可以把一个对象传给点赞组件作为参数:

class Index extends Component {
  render () {
    return (
      <div>
        <LikeButton wordings={{likedText: '已赞', unlikedText: '赞'}} />
      </div>
    )
  }
}

这里把likedTextunlikedText两个参数封装到一个wordings的对象参数中,然后传入点赞组件LikeButton里面.
然后,点赞按钮内部就要用this.props.wordings来获取到参数了:

const wordings = this.props.wordings || {
      likedText: '取消',
      unlikedText: '点赞'
    }
  • 把函数设为props
    如:
//Index控件的定义
class Index extends Component {
  render () {
    return (
      <div>
        <LikeButton
          wordings={{likedText: '已赞', unlikedText: '赞'}}
          onClick={() => console.log('Click on like button!')}/>
      </div>
    )
  }
}

解析:以上代码的wordingsonClick都被看作是LikeButtonprops。这样可以通过this.props.onClick获取到传进去的函数,修改LikeButtonhandleClickOnLikeButton方法测试一下:

...
  handleClickOnLikeButton () {
    this.setState({
      isLiked: !this.state.isLiked
    })
    if (this.props.onClick) {
      this.props.onClick()
    }
  }
...

当每次点击按钮的时候,控制台会显示 Click on like button!。但这个行为不是点赞组件自己实现的,而是我们传进去的。所以,一个组件的行为、显示形态都可以用 props 来控制,就可以达到很好的可配置性。

  • 默认配置defaultProps
class LikeButton extends Component {
  static defaultProps = {
    likedText: '取消',
    unlikedText: '点赞'
  }
  ...
}

使用了defaultProps之后,在render函数中,我们可以直接使用this.props而不需要再做判断。

  • props不可变
    很重要的一点,props一旦传入进来之后就不能改变。比如,以下代码是会报错的:
...
  handleClickOnLikeButton () {
    this.props.likedText = '取消'//此处报错
    this.setState({
      isLiked: !this.state.isLiked
    })
  }
...

但是
这并不意味着由 props 决定的显示形态不能被修改。组件的使用者可以主动地通过重新渲染的方式把新的 props 传入组件当中,这样这个组件中由 props决定的显示形态也会得到相应的改变。比如:

//在Index组件中
class Index extends Component {
  constructor () {
    super()
    this.state = {
      likedText: '已赞',
      unlikedText: '赞'
    }
  }

  handleClickOnChange () {
    this.setState({
      likedText: '取消',
      unlikedText: '点赞'
    })
  }

  render () {
    return (
      <div>
        <LikeButton
          likedText={this.state.likedText}
          unlikedText={this.state.unlikedText} />
        <div>
          <button onClick={this.handleClickOnChange.bind(this)}>
            修改 wordings
          </button>
        </div>
      </div>
    )
  }
}

这里,由于用户点赞,导致state改变;setState 会导致 Index 重新渲染,所以 LikedButton 会接收到新的 props,并且重新渲染,于是它的显示形态也会得到更新。这就是通过重新渲染的方式来传入新的 props 从而达到修改 LikedButton 显示形态的效果。

  • state vs props
    state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。

渲染列表数据

假如要渲染一个用户列表数据:

const users = [
  { username: 'Jerry', age: 21, gender: 'male' },
  { username: 'Tomy', age: 22, gender: 'male' },
  { username: 'Lily', age: 19, gender: 'female' },
  { username: 'Lucy', age: 20, gender: 'female' }
]

可以使用map渲染列表数据:

class Index extends Component {
  render () {
    const usersElements = [] // 保存每个用户渲染以后 JSX 的数组
    for (let user of users) {
      usersElements.push( // 循环每个用户,构建 JSX,push 到数组中
        <div>
          <div>姓名:{user.username}</div>
          <div>年龄:{user.age}</div>
          <div>性别:{user.gender}</div>
          <hr />
        </div>
      )
    }
    return (
      <div>{usersElements}</div>
    )
  }
}

这里用了一个新的数组 usersElements,然后循环 users 数组,为每个 user 构建一个 JSX 结构,然后 push 到 usersElements 中。然后直接用表达式插入,把这个 userElements插到 return 的 JSX 当中。

优化:
可以直接用ES6自带的map:

class Index extends Component {
  render () {
    return (
      <div>
        {users.map((user) => {
          return (
            <div>
              <div>姓名:{user.username}</div>
              <div>年龄:{user.age}</div>
              <div>性别:{user.gender}</div>
              <hr />
            </div>
          )
        })}
      </div>
    )
  }
}

继续优化,进一步把渲染单独一个用户的结构抽离出来作为一个单独的组件:

class User extends Component{
    render(){
        const {user}  = this.props 
        //这段代码你可以认为是这样:
        //const user = this.props.user;
        //这是 ES6 的简写形式。
        return (
            <div>
                <div>name: {user.username}</div>
                <div>age: {user.age}</div>
                <div>gender: {user.gender}</div>
                <hr/>
            </div>
        )
    }
}
class UserGroup extends Component{
    render() {
        return (
            <div>
                {users.map(user => <User user={user}/>)}
            </div>
        )
    }

}

key

为了让DOM高效运行,对于用表达式套数组罗列到页面上的元素,都要为每个元素加上 key 属性,这个 key 必须是每个元素唯一的标识。一般来说,key 的值可以直接后台数据返回的 id,因为后台的 id 都是唯一的。可以把上面的代码改成这样:

class Index extends Component {
  render () {
    return (
      <div>
        {users.map((user, i) => <User key={i} user={user} />)}
      </div>
    )
  }
}

前端应用状态管理 —— 状态提升

我们将这种组件之间共享的状态交给组件最近的公共父节点保管,然后通过 props 把状态传递给子组件,这样就可以在组件之间共享数据了。

在我们的例子当中,如果把 comments 交给父组件 CommentApp ,那么 CommentList 和 CommentList2 都可以通过 props 获取到 comments,React.js 把这种行为叫做“状态提升”。

总结一下:当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或着影响的行为。

你会发现这种无限制的提升不是一个好的解决方案。一旦发生了提升,你就需要修改原来保存这个状态的组件的代码,也要把整个数据传递路径经过的组件都修改一遍,好让数据能够一层层地传递下去。这样对代码的组织管理维护带来很大的问题。到这里你可以抽象一下问题:

如何更好的管理这种被多个组件所依赖或影响的状态?

所以我们后续会引入 Redux 这样的状态管理工具来帮助我们来管理这种共享状态,但是在讲解到 Redux 之前,我们暂时采取状态提升的方式来进行管理。

挂载阶段的组件生命周期

这一节我们来讨论一下对于一个组件来说,constructorcomponentWillMountcomponentDidMountcomponentWillUnmount这几个方法在一个组件的出生到死亡的过程里面起了什么样的作用。

  • constructor
    所有关于组件自身的状态的初始化工作都放在constructor里面去做。比如说初始化state.

  • componentWillMount
    一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行:

...
  componentWillMount () {
    ajax.get('http://json-api.com/user', (userData) => {
      this.setState({ userData })
    })
  }
...

修改这个 Index 让这个时钟可以隐藏或者显示:

class Index extends Component {
  constructor () {
    super()
    this.state = { isShowClock: true }
  }

  handleShowOrHide () {
    this.setState({
      isShowClock: !this.state.isShowClock
    })
  }

  render () {
    return (
      <div>
        {this.state.isShowClock ? <Clock /> : null }
        <button onClick={this.handleShowOrHide.bind(this)}>
          显示或隐藏时钟
        </button>
      </div>
    )
  }
}

这里,因为隐藏时钟的时候,并没有清楚定时器,所以它被隐藏了之后还在不停地尝试setState. 这时候就要用componentWillUnmount.

  • componentWillUnmount
    在组件销毁的时候,做这种清场的工作。例如清除该组件的定时器和其他的数据清理工作。我们给 Clock 添加 componentWillUnmount,在组件销毁的时候清除该组件的定时器:
...
  componentWillUnmount () {
    clearInterval(this.timer)
  }
...

总结
我们一般会把组件的 state 的初始化工作放在 constructor 里面去做;在 componentWillMount 进行组件的启动工作,例如 Ajax 数据拉取、定时器的启动;组件从页面上销毁的时候,有时候需要一些数据的清理,例如定时器的清理,就会放在 componentWillUnmount 里面去做。

说一下本节没有提到的 componentDidMount 。一般来说,有些组件的启动工作是依赖 DOM 的,例如动画的启动,而 componentWillMount 的时候组件还没挂载完成,所以没法进行这些启动工作,这时候就可以把这些操作放在 componentDidMount 当中。componentDidMount 的具体使用我们会在接下来的章节当中结合 DOM 来讲。

更新阶段的组件生命周期

这里为了知识的完整,补充关于更新阶段的组件生命周期:

  1. shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
  2. componentWillReceiveProps(nextProps):组件从父组件接收到新的 props 之前调用。
  3. componentWillUpdate():组件开始重新渲染之前调用。
  4. componentDidUpdate():组件重新渲染并且把更改变更到真实的 DOM 以后调用。

ref 和 React.js 中的 DOM 操作

React.js 并不能完全满足所有 DOM 操作需求,有些时候我们还是需要和 DOM 打交道。比如说你想进入页面以后自动 focus 到某个输入框,你需要调用 input.focus() 的 DOM API,比如说你想动态获取某个 DOM 元素的尺寸来做后续的动画,等等。

React.js 当中提供了 ref 属性来帮助我们获取已经挂载的元素的 DOM 节点,你可以给某个 JSX 元素加上 ref属性:

class AutoFocusInput extends Component {
  componentDidMount () {
    this.input.focus()
  }

  render () {
    return (
      <input ref={(input) => this.input = input} />
    )
  }
}

ReactDOM.render(
  <AutoFocusInput />,
  document.getElementById('root')
)

这里我们给input元素加了一个ref属性,这个属性值是一个函数。当input元素在页面挂载完成之后,react就会调用这个函数。在函数中,这个DOM元素被设置为组件实例的一个属性,这样以后可以通过this.input获取到这个DOM元素。

然后我们就可以在 componentDidMount 中使用这个 DOM 元素,并且调用 this.input.focus() 的 DOM API。整体就达到了页面加载完成就自动 focus 到输入框的功能(大家可以注意到我们用上了 componentDidMount 这个组件生命周期)。

我们可以给任意代表 HTML 元素标签加上 ref 从而获取到它 DOM 元素然后调用 DOM API。但是记住一个原则:能不用 ref 就不用。

练习:

//完成 Post 组件,接受一个字符串的 content 作为 props,Post 会把它显示到自己的 <p> 元素内。并且,点击 <p> 元素的时候,会使用 console.log 把元素的高度打印出来。
class Post extends Component {
  render () {
    return (
      <p ref={(p) => this.p = p } 
          onClick={() => console.log(this.p.clientHeight)}>{this.props.content}</p>
      )
  }
}

props.children 和容器类组件

目标,能写成以下代码的形式:(其中Card是一个组件)

ReactDOM.render(
  <Card>
    <h2>React.js 小书</h2>
    <div>开源、免费、专业、简单</div>
    订阅:<input />
  </Card>,
  document.getElementById('root')
)

实际上,React.js 默认就支持这种写法,所有嵌套在组件中的 JSX 结构都可以在组件内部通过 props.children 获取到:

class Card extends Component {
  render () {
    return (
      <div className='card'>
        <div className='card-content'>
          {this.props.children}
          //注意这句话
        </div>
      </div>
    )
  }
}

把 props.children 打印出来,你可以看到它其实是个数组:

我们甚至可以在组件内部把数组中的 JSX 元素安置在不同的地方:

class Layout extends Component {
  render () {
    return (
      <div className='two-cols-layout'>
        <div className='sidebar'>
          {this.props.children[0]}
        </div>
        <div className='main'>
          {this.props.children[1]}
        </div>
      </div>
    )
  }
}

这是一个两列布局组件,嵌套的 JSX 的第一个结构会成为侧边栏,第二个结构会成为内容栏,其余的结构都会被忽略。这样通过这个布局组件,就可以在各个地方高度复用我们的布局。

dangerouslySetHTML 和 style 属性

(to be continued.)

PropTypes和组件参数验证

为了避免JavaScript弱类型带来的缺陷,这里引入一个Proptypes参数验证,即可以验证是否已传入所需要的参数,传入的参数是否是目标类型,等等。

我们这里先安装一个 React 提供的第三方库 prop-types:
npm install --save prop-types
它可以帮助我们验证 props 的参数类型,例如:

import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Comment extends Component {
  static propTypes = {
    comment: PropTypes.object
    //这里验证传入的comment是否是object类型
  }

  render () {
    const { comment } = this.props
    return (
      <div className='comment'>
        <div className='comment-user'>
          <span>{comment.username} </span>:
        </div>
        <p>{comment.content}</p>
      </div>
    )
  }
}

另外,为了避免所需的props没有被初始化,可以加一个默认值要求:

...
static propTypes = {
  comment: PropTypes.object.isRequired
}
...

React.js 提供的 PropTypes 提供了一系列的数据类型可以用来配置组件的参数:

PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
...

总结
通过 PropTypes 给组件的参数做类型限制,可以在帮助我们迅速定位错误,这在构建大型应用程序的时候特别有用;另外,给组件加上 propTypes,也让组件的开发、使用更加规范清晰。

高阶组件(Higher-Order Components)

高阶组件就是一个函数,传给它一个组件,它返回一个新的组件。

const NewComponent = higherOrderComponent(OldComponent)

所以,注意,高阶组件是一个函数,而不是组件。我们看看一个很简单的高阶组件:

import React, { Component } from 'react'

export default (WrappedComponent) => {
  class NewComponent extends Component {
    // 可以做很多自定义逻辑
    render () {
      return <WrappedComponent />
    }
  }
  return NewComponent
}

现在看来好像什么用都没有,它就是简单的构建了一个新的组件类 NewComponent,然后把传进入去的 WrappedComponent 渲染出来。但是我们可以给 NewCompoent 做一些数据启动工作:

import React, { Component } from 'react'

export default (WrappedComponent, name) => {
//这函数内定义了一个新组件`NewComponent`
  class NewComponent extends Component {
    constructor () {
      super()
      this.state = { data: null }
    }

    componentWillMount () {
      let data = localStorage.getItem(name)
      //根据函数的第二个参数`name`在挂在阶段从LocalStorage加载数据,并且setState到自己的state.data中。而render的时候再把`this.state.data`通过props.data传给WrappedComponent。
      this.setState({ data })
    }

    render () {
      return <WrappedComponent data={this.state.data} />
    }
  }
  return NewComponent
}

这个高阶组件有什么用呢?
假设上面的代码是在 src/wrapWithLoadData.js 文件中的,我们可以在别的地方这么用它:

import wrapWithLoadData from './wrapWithLoadData'

class InputWithUserName extends Component {
  render () {
    return <input value={this.props.data} />
  }
}

InputWithUserName = wrapWithLoadData(InputWithUserName, 'username')
//注意这里的用法
export default InputWithUserName

假如 InputWithUserName 的功能需求是挂载的时候从 LocalStorage 里面加载 username 字段作为 <input /> 的 value 值,现在有了 wrapWithLoadData,我们可以很容易地做到这件事情。

只需要定义一个非常简单的 InputWithUserName,它会把 props.data 作为 <input /> 的 value 值。然把这个组件和 ‘username’ 传给 wrapWithLoadData,wrapWithLoadData 会返回一个新的组件,我们用这个新的组件覆盖原来的 InputWithUserName,然后再导出去模块。

别人用这个组件的时候实际是用了被加工过的组件:

import InputWithUserName from './InputWithUserName'

class Index extends Component {
  render () {
    return (
      <div>
        用户名:<InputWithUserName />
      </div>
    )
  }
}

高阶组件的灵活性
代码复用的方法、形式有很多种,你可以用类继承来做到代码复用,也可以分离模块的方式。但是高阶组件这种方式很有意思,也很灵活。学过设计模式的同学其实应该能反应过来,它其实就是设计模式里面的装饰者模式。它通过组合的方式达到很高的灵活程度。

React.js的context

React.js 的 context 就是这么一个东西,某个组件只要往自己的 context 里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的 context 只有它的子组件能够访问,它的父组件是不能访问到的,你可以理解每个组件的 context 就是瀑布的源头,只能往下流不能往上飞。

我们在父组件里定义一个context:

class Index extends Component {
  static childContextTypes = {
    themeColor: PropTypes.string
  }
    constructor () {
    super()
    this.state = { themeColor: 'red' }
  }

  getChildContext () {
    return { themeColor: this.state.themeColor }
  }
  ...
}

然后修改子组件,使其能获取这个状态:

class Title extends Component {
  static contextTypes = {
    themeColor: PropTypes.string
  }
  render () {
    return (
      <h1 style={{ color: this.context.themeColor }}>React.js 小书标题</h1>
    )
  }
}

子组件要获取 context 里面的内容的话,就必须写 contextTypes 来声明和验证你需要获取的状态的类型,它也是必写的,如果你不写就无法获取 context 里面的状态。Title 想获取 themeColor,它是一个字符串,我们就在 contextTypes 里面进行声明。

接下来就开始redux的学习啦。
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值