Legendary

关于React你需要知道的13件事 - aimforsimplicity.com

Legendary · 2017-01-09翻译 · 870阅读 原文链接

React

图片来自Franco Folini

我已经使用React一年多了。我也正在进行培训,帮助人们从头开始学习Redux。 我注意到,在每次训练中,我一遍又一遍地解释着一些相同的概念。 如果你想“谈论React”的话,我认为这些概念是必不可少的。如果你正处于学习的中间阶段,你可能有兴趣阅读这篇文章。

1) 它不是一个框架

Angular或Ember是那些已经帮你做了一些决定的框架。React只是一个库,你需要自己做出所有的决定。它专注于帮助您使用组件构建用户界面。

它不会帮助你与服务器通信、翻译、路由等。有些人认为这是一个弱点。一个聪明的人曾经说过:

“框架解决了其创作者的问题”——我的导师之一

React是很薄(thin)的,它很容易与其他第三方库混合使用。 丰富的JS生态系统对于任何事情都有一个库。你可以选择你最喜欢的一个,插入它,而不用受框架的设计限制。

当然,有一个famous js fatigue。如果你觉得是这么回事并且不想自己做出每一个决定,那就做一个30分钟的研究,选择一个 opinionated starter kits/boilerplates,更改任何你想要的然后只要使用它就可以了。

2) JSX

当你在查看React的例子时候,可能已经见过JSX了。但React代码也可以用纯JS代码来编写:

const rootElement =
  React.createElement('div', {},
    React.createElement('h1', {style: {color: 'red'}}, 'The world is yours'),
    React.createElement('p', {}, 'Say hello to my little friend')
  )

ReactDOM.render(rootElement, document.getElementById('app'))

有些人不喜欢将整个标记代码编写为函数调用。这可能是为什么Facebook上的人想出了JSX - 一个“React.createElement(component, props, …children)的语法糖方法”。 这就是为什么我们可以重构上面的例子:

const RootElement = (
  <div>
    <h1 style=>The world is yours</h1>
    <p>Say hello to my little friend</p>
  </div>
)

ReactDOM.render(RootElement, document.getElementById('app'))

在构建过程中Babel会将标记转换为纯JS代码。

3) 这是JavaScript

要在React中动态生成标记,还可以使用JS:

<select value={this.state.value} onChange={this.handleChange}>
  {somearray.map(element => <option value={element.value}>{element.text}</option>)}
</select>

在上面的例子中,somearray数组使用map函数映射到<option>元素的列表。这里唯一偏离正常HTML的是在<select>元素上的一个value,这是为你设置selected属性。

虽然没有必要使用它:

<select onChange={this.handleChange}>
  {somearray.map(element => (
      <option
        value={element.value}
        selected={this.state.value === element.value}
      >
        {element.text}
      </option>
    ))}
</select>

许多流行的框架使用模板语言来进行上述操作。每个框架都有自己的模板语言。所以每次你想使用一个框架,你都需要学习一种新的模板语言以及它的怪癖和缺点。

上面的代码带有关于缺少key属性的警告。 要知道为什么和如何解决可以访问[这里](https://facebook.github.io/react/docs/lists-and-keys.html),或只是读取在你的开发工具里的错误信息。

我记得我第一次在Angular 1中使用control。有一个特殊的ngOptions指令,将为你生成所有可能的选项。

<select
  ng-model="selectedItem"
  ng-options="item as item.name for item in items">
</select>

至今我都不知道item as item.name for item 的意思。

有一些比我聪明的人,他们记住了如何使用各种模板语言。而我经常从后端切换到前端开发。有很长一段时间,我根本不做前端。如果你不使用它,知识就会消失。这就是为什么记忆不适合我。但是我熟悉map()函数和HTML,所以React的方式是非常吸引我的。

另一个好处是静态代码分析是免费的。它比自定义模板标记更容易检测JS。

对我来说,模板是基于字符串开发。没有严格的提示。只是一些在HTML中,没有以任何方式检查。 JSX给JS增加了更多的东西,使它更强大。这也是我不同意当人们把JSX叫做模板语言的原因。

4) 它是声明式的

在React中,你可以使用声明的方式来编写组件。让我们用<select>来看看上面的例子:

<select value={this.state.value} onChange={this.handleChange}>
  {somearray.map(element => <option value={element.value}>{element.text}</option>)}
</select>

在这个</select>例子中,你没有使用for循环来手动创建映射集合。你不是说它应该做什么,而是它应该是什么样子。

5) 分离关注点

在React中,你通常作为一个组件保留HTML,JS和CSS。如果你想在网格上显示一行,你创建一个Row组件,并将HTML,逻辑和行为放在一个文件中。

多年来,我们将JS,HTML和CSS拆分为不同的文件。 Pete Hunt称分离技术。它不应该与分离关注点的概念混淆。

我经常得到问题我是否认为这种“缺乏分离”奇怪。但是对我来说奇怪的是,人们经常给出的例子来捍卫保持HTML和JS分离的策略:

“如果你将HTML和JS保存在单独的文件中,你可以轻松地替换HTML并保持JS完好无损。

如果你仔细想一想,它并不有效的。对HTML结构的大多数更改都需要重构JS逻辑。

“显示逻辑和标记不可避免地紧密耦合” —— Pete Hunt

如果将文本输入更改为复选框,则离不开需要重写逻辑。

6) 数据传递

在React中,数据沿着组件的树向下传递。如果要将数据从父组件传递到子组件,则需要使用props。从JSX的角度看,props就是HTML属性。

父组件:

<div>
  <Greetings color={red} text='Hello' />
</div>

在子组件中,props在this.props下可用。

子组件:

const Greetings = React.createClass({
  render () {
    const {color, text} = this.props
    const divStyle = {padding: 10, backgroundColor: 'black'}
    const headingStyle = {color: color}

    return (
      <div style={divStyle}>
        <h1 style={headingStyle}>{text}</h1>
      </div>
    )
  }
})

7) State

到目前为止,我们仅讨论了静态组件与静态数据传递到组件树。通常,需要创建一个有状态的组件,其中状态随时间而变化。让我们考虑一个可以输入本文并在下方显示出来的组件。

const InputBox = React.createClass({
  getInitialState () {
    return {
      text: ''
    }
  },

  changeText (event) {
    this.setState({text: event.target.value})
  },

  render () {
    return (
      <div>
        <input type='text' onChange={this.changeText} placeholder='text' value={this.state.text} />
        <span>{this.state.text}</span>
      </div>
    )
  }
})

在开始时,您设置了组件的默认状态。在这种情况下,我们想要有一个空的text值。要做到这一点,你可以使用组件方法getInitialState(),这个方法必须返回一个组件的状态对象。

为了更新状态,事件处理程序changeText()被分配给onChange事件。要更新状态React期望你使用内置的setState()方法。

组件状态完成更新后将重新渲染。 setState()调用需要用来通知React关于挂起状态的变化,以便它可以应用更改。如果有事情发生了变化,不会有任何形式的循环。

你需要记住,setState()是异步的。结果不会立即生效。下面的示例将同时展示在应用更改后立即访问状态的错误的和良好的方式:

// 错误的:
// ...
someFunction (value) {
  this.setState({someValue: value})
  // 可能不会在此时更改
  console.log('New value: ', this.state.someValue)
}
// ...
// 良好的:
// ...
someFunction (value) {
  this.setState({someValue: value}, () => {
    // 用新的state做一些事
    console.log('New value: ', this.state.someValue)
  })
}
// ...

好的,但如果需要传播状态的变化到父组件呢?

8) 事件上升

让我们假设我们有一个Parent组件,需要从上一个示例的子InputBox中获取数据。

const Parent = React.createClass({
  gimmeThatState (textFromInput) {
    console.log(textFromInput)
    // or this.setState({text: textFromInput})
  },

  render () {
    <InputBox pushChangesUp={this.gimmeThatState} />
  }
})

Parent组件传递一个函数(作为一个prop)以便可以使用InputBox来推送一些数据。

更改后的InputBox可能如下所示:

const InputBox = React.createClass({
  propTypes: {
    pushChangesUp: React.PropTypes.func.isRequired
  },

  getInitialState () {
    return {
      text: ''
    }
  },

  changeText (event) {
    this.setState({text: event.target.value})
  },

  pushChangesUp () {
    this.props.pushChangesUp(this.state.text)
  }

  render () {
    return (
      <div>
        <input type='text' onChange={this.changeText} placeholder='text' value={this.state.text} />
        <span>{this.state.text}</span>
        <button onClick={this.pushChangesUp}>Push changes up</button>
      </div>
    )
  }
})

你看到的第一件事是在组件声明的开头有一个propTypes属性。它用于验证属性。对我来说,它的作用类似于OOP中的接口。通过查看propTypes我立即知道我需要提供给组件什么属性,使其工作。

接下来,局部pushChangesUp()事件处理程序被分配给按钮上的onClick事件。当单击时,该函数使用pushChangesUp()从props中推送数据。

现在,Parent组件可以将数据保存到自己的state中,并将其传递给不同的组件。

9) 渲染如何工作

每个setState()方法的调用都会通知React状态发生了变化。然后,React调用render()方法来更新内存中的组件表现内容(Virtual DOM,并将其与在浏览器中呈现的内容进行比较。如果有更改,React会对DOM进行尽可能小的更新。

子组件知道他们需要重新渲染,因为他们的props改变了。

我经常用Git上的差异(diff)机制类比这个概念。有了两个组件树的快照,React比较然后只要交换需要交换的内容。

我正在寻找一个聪明的图描述渲染流,但没有找不到。但是关于这个概念,你可以在这里阅读更多。

10) 组合是关键

拥有状态的父组件通常称为容器组件。它们负责状态管理和渲染子组件(这听起来很奇怪)。子组件用于触发从父类传递的事件处理程序(如前面示例中的InputBox组件)并显示数据。 负责显示数据的子组件称为展示组件

容器组件通常负责获取数据,API调用(请参阅componentDidMount()生命周期方法)等。您应该将它保存在一个地方,以避免展示组件中的副作用。容器组件应该尽可能无视一切,而不是展示数据。

这种关注和术语的分离由Dan Abramov(Redux的作者)推广。你可以在他的文章中阅读更多。

你可以看到,这一切都适合在一起。当每个组件部分遵循单一责任时,它可以与其他组件并重复使用。

最大的挑战是弄清楚如何划分这些责任和在哪里放置状态。如果你想知道更多关于这个主题,搜索“thinking in react”的文章。

11) 保持state小

在我的训练中,经常有涉及某种列表过滤的练习。假设你有一个todos列表:

const initialState = [
  {id: 1, text: 'laundry'},
  {id: 2, text: 'shopping'}
  // ...
]

const List = React.createClass({
  getInitialState () {
    return {
      todos: initialState
    }
  },

  render () {
    return (
      <div>
        <ul>
          {this.state.todos.map(todo => <li key={todo.id}>{todo.text}</li>)}
        </ul>
      </div>
    )
  }
})

现在,您要添加一个搜索框。 50%的人会采用这种方法的变形:

 const initialState = [
  {id: 1, text: 'laundry'},
  {id: 2, text: 'shopping'}
  // ...
]

const List = React.createClass({
  getInitialState () {
    return {
      todos: initialState,
      filteredTodos: null
    }
  },

  search (searchText) {
    const filteredTodos = this.state.todos(todo => todo.text.indexOf(searchText) > 0)

    this.setState({filteredTodos: filteredTodos})
  },

  render () {
    const {filteredTodos, todos} = this.state // get todos from state

    const list = filteredTodos === null ? todos : filteredTodos // if there are filtered todos use them

    return (
      <div>
        <SearchBox onChange={this.search} />
        <ul>
          {list.map(todo => <li key={todo.id}>{todo.text}</li>)}
        </ul>
      </div>
    )
  }
})

你可以看到这里是重复的状态,两个真相的来源和意大利面条式代码的介绍。想象一下,你想更新一个待办事项并启用搜索过滤器。

怎样更好呢?保持状态尽可能小。如果某事可以在飞行中计算,那么它就应该可以。

这有一个更好的方式:

const initialState = [
  {id: 1, text: 'laundry'},
  {id: 2, text: 'shopping'}
  // ...
]

const List = React.createClass({
  getInitialState () {
    return {
      todos: initialState,
      searchText: null
    }
  },

  search (searchText) {
    this.setState({searchText: searchText})
  },

  filter (todos) {
    if (!this.state.searchText) {
      return todos
    }

    return todos.filter(todo => todo.text.indexOf(this.state.searchText) > 0)
  },

  render () {
    return (
      <div>
        <SearchBox onChange={this.search} />
        <ul>
          {this.filter(list).map(todo => <li key={todo.id}>{todo.text}</li>)}
        </ul>
      </div>
    )
  }
})

这里你只保留searchText,用于在渲染之前过滤列表。一个更简洁的方法,一个更小的状态并且没有重复。

12) 条件渲染

在前面的示例中,根据一些条件逻辑呈现列表。如果满足一些条件,通常需要渲染一部分标记,如果没有满足,则需要渲染另一部分。在React中有几种方法可以做到这一点。

三元运算符

在JSX中,你可以使用三元运算符 去处理条件渲染:

React.createClass({
  getInitialState () {
    return {
      hideTodos: true
    }
  },

  render () {
    return (
      <div>
      {
        hideTodos ? 'Sorry there is no data' : <TodoList />
      }
      </div>
    )
  }
})

可能的变动:

  • return表达式之外的三元运算符

  • return表达式之外的if/else块

辅助方法

React.createClass({
  getInitialState () {
    return {
      hideTodos: true
    }
  },

  renderTodos () {
    if (this.state.hideTodos) {
      return 'Sorry there is no data'
    }

    return <TodoList />
  }

  render () {
    return (
      <div>
      {
        this.renderTodos()
      }
      </div>
    )
  }
})

这是一个有用的方法,但是当组件更大时,你需要在辅助方法和render()方法之间上下跳跃。

一个组件

因为一个功能切换工作的很好,这可能是一个最简洁的方法了。?????

React.createClass({
  getInitialState () {
    return {
      hideTodos: true
    }
  },

  render () {
    return (
      <div>
        <HideIf condition={this.state.hideTodos}>
          <TodoList />
        </HideIf>
      </div>
    )
  }
})

const HideIf = React.createClass({
  render () {
    if (this.props.condition) {
      return <span>'Sorry there is no data'</span>
    }

    return this.props.children // children is what's inside <HideIf> element
  }
})

13) 你不需要Flux

可能在React新手中最常见的误解是,你需要使用与Redux或一些其他的Flux实现一起使用。

Redux是很好的。它是最流行的Flux实现,并且提供了大量的功能和简洁、实用的,可测试的方法,感谢伟大的教程。但[你可能不需要它】(https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367#.cdf363ebw)。

如果你正在学习React,如果你的应用程序很小,如果你不需要全局状态,或者你没有任何需要跟踪应用程序状态更改的问题——不要使用它。 如果你想知道更多,请阅读这里

总结

所以,这是我和别人讨论最多的React概念的列表。我强烈建议阅读React docs,因为它是从头到尾最好的知识来源。我还建议观看早期的React视频(从2013年到2014年),这些视频描述了Facebook在创建React时尝试解决的问题。它会帮助你认识到如果你有类似的问题的时候,React会帮助你还是你应该坚持一些其他的技术。

相关文章