Skip to content

React

React 是一个用于构建用户界面的 JAVASCRIPT

特点

  • 1.声明式设计 −React 采用声明范式,可以轻松描述应用。
  • 2.高效 −React 通过对 DOM 的模拟,最大限度地减少与 DOM 的交互。
  • 3.灵活 −React 可以与已知的库或框架很好地配合。
  • 4.JSX − JSX 是 JavaScript 语法的扩展。React 开发不一定使用 JSX ,但我们建议使用它。
  • 5.组件 − 通过 React 构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。
  • 6.单向响应的数据流 − React 实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。

安装

  • react.min.js - React 的核心库
  • react-dom.min.js - 提供与 DOM 相关的功能
html
<!-- 开发环境 -->
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- 生产环境 -->
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>

使用 JSX,则 <script> 标签的 type 属性需要设置为 text/babel

设置 crossorigin 属性:有更好的错误处理体验。参考

  • 设置了crossorigin就相当于开启了cors校验。
  • 开启 cors 校验之后,跨域的 script 资源在运行出错的时候,window.onerror可以捕获到完整的错误信息。
  • crossorigin=use-credentials可以跨域带上 cookie

Create React App

创建新的单页应用的最佳方式

shell
# 安装 create-react-app
cnpm install -g create-react-app

# 创建react
create-react-app my-app
npx create-react-app

cd my-app
npm start

Next.js

一个流行的、轻量级的框架,用于配合 React 打造静态化和服务端渲染应用。它包括开箱即用的样式和路由方案

Gatsby

创建静态网站的最佳方式

React JSX

jsx:javascript + xml

jsx
const element = <h1 className="greeting">Hello, world!</h1>

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用

jsx
const element = React.createElement('h1', { className: 'greeting' }, 'Hello, world!')

特点

  • JSX 执行更快,在编译为 JavaScript 代码后进行了优化
  • 类型安全,编译过程中发现错误
  • 使用 JSX 编写模板更加简单快速

JSX 引入

babel.min.js - Babel 可以将 ES6 代码转为 ES5 代码。Babel 内嵌了对 JSX 的支持

cdn

html
<!-- 生产环境中不建议使用 -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

<script type="text/babel"></script>

JSX 添加到项目

shell
npm init -y
npm install babel-cli@6 babel-preset-react-app@3

mkdir src
npx babel --watch src --out-dir . --presets react-app/prod

嵌入表达式

  • 遇到 <>HTML解析
  • {}JavaScript解析
  • 存在标签结构,且标签结构需要换行,用()包裹
jsx
ReactDOM.render(
  <div>
    <h1>菜鸟教程</h1>
    {/*注释...*/}
  </div>,
  document.getElementById('example')
)

JSX 里的 class 变成了 className,而 tabindex 则变为 tabIndex

JSX 防止注入攻击:React DOM 在渲染所有输入内容之前,默认会进行转义

样式React 推荐使用内联样式。使用 camelCase 语法来设置内联样式,会在指定元素数字后自动添加 px

:外层{}为 变量解析,内层{}为 样式对象

jsx
<ListItem style={{ color: red, fontSize: 12 }} />

数组:自动展开

jsx
var arr = [<h1>菜鸟教程</h1>, <h2>学的不仅是技术,更是梦想!</h2>]
ReactDOM.render(<div>{arr}</div>, document.getElementById('example'))

嵌入:允许在大括号中嵌入任何表达式

jsx
function NumberList(props) {
  const numbers = props.numbers
  return (
    <ul>
      {numbers.map((number) => (
        <ListItem key={number.toString()} value={number} />
      ))}
    </ul>
  )
}

元素渲染 - render

React 构建的应用通常只有单一的根 DOM 节点

React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。

据已学知识,更新 UI 唯一的方式是创建一个全新的元素,并将其传入 ReactDOM.render()

ReactDOM.render() :首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分

js
function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  )
  ReactDOM.render(element, document.getElementById('root'))
}

setInterval(tick, 1000)

ES6,props 变为 this.props

components

组件定义

组件名称必须以大写字母开头。小写代表原生 DOM,大写代表 组件。

jsx
// 函数组件
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>
}
// ES6类写法
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

自定义组件

原生 HTML 元素名以小写字母开头,而自定义的 React 类名大写字母开头

使用props 向组件传参

因为 classforJavaScript 保留字。添加属性时, class 属性需要写成 classNamefor 属性需要写成 htmlFor

组件 API

  • 设置状态:setState
  • 替换状态:replaceState
  • 设置属性:setProps
  • 替换属性:replaceProps
  • 强制更新:forceUpdate
  • 获取 DOM 节点:findDOMNode
  • 判断组件挂载状态:isMounted

setState - 设置状态

setState(object nextState[, function callback])

  • nextState设置新状态新旧状态会进行合并
  • callback(可选):回调函数。在*setState设置成功*,且组件重新渲染调用

replaceState - 替换状态

replaceState(object nextState[, function callback])

  • nextState设置新状态,新状态替换旧状态将要设置的新状态
  • callback(可选):回调函数。在*setState设置成功*,且组件重新渲染调用

setProps - 设置属性

setProps(object nextProps[, function callback])

  • nextProps设置新属性新旧属性会进行合并
  • callback(可选):回调函数。在*setProps设置成功*,且组件重新渲染调用

props:从父组件向下传递至所有的子组件。

setProps()向组件传递数据通知setProps()组件重新渲染

更新组件

  • 再次调用React.render()
  • setProps()方法改变组件属性,触发组件重新渲染。

replaceProps - 替换属性

replaceProps(object nextProps[, function callback])

  • nextProps设置的新属性,该属性会替换当前的props
  • callback(可选):回调函数。在*replaceProps设置成功*,且组件重新渲染调用

forceUpdate - 强制更新

forceUpdate([function callback])

  • callback(可选):回调函数。会在组件render()方法调用后调用

父组件和子组件均会调用自身 render(),组件重新渲染时,会读取this.propsthis.state,没有改变,只更新 DOM。

应用:this.propsthis.state之外的组件重绘(如:修改 this.state),用于通知React需要调用render()

避免使用 forceUpdate

findDOMNode - 获取 DOM 节点

DOMElement findDOMNode()

  • 返回值:DOM 元素DOMElement

组件已挂载到 DOM 中,会返回对应的本地浏览器 DOM 元素。

render返回nullfalsefindDOMNode也返回null

应用:从DOM中取值

isMounted - 判断组件挂载状态 ^已废弃?^

bool isMounted()

  • 返回值:truefalse,表示组件是否 已挂载到 DOM

应用:保证了setState()forceUpdate()异步调用不会出错

isMounted 的方法在 ES6 中*?已经废除*。

原因:不足以检测组件是否挂载,对于异步的程序情况,以及逻辑上造成混乱。用以下方法代替:

jsx
componentDidMount() {
    this.mounted = true;
}

componentWillUnmount() {
    this.mounted = false;
}

组件之间传值

子传父 :通过 父元素传给 一个能改变父元素state的回调

父传子:通过 props

兄弟之间:共同子元素,或共同父元素

State

React 把组件看成一个状态机State Machines)。通过与用户的交互,实现不同状态,然后渲染 UI,让用户界面和数据保持一致。只需更新组件的 state,然后根据新的 state 重新渲染用户界面不要操作 DOM

不要直接更新状态:直接更新不会重新渲染组件。

解决:setState

jsx
// Wrong
this.state.comment = 'Hello'
// Correct
this.setState({ comment: 'Hello' })

状态更新可能为异步this.propsthis.state 可能是异步更新。

解决:接受一个函数而不是对象

jsx
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment
})
// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}))

setState总会触发组件重绘,除非在shouldComponentUpdate()实现条件渲染逻辑

获取最新值

通过setState回调

jsx
this.setState(
  {
    count: this.state.count + 1
  },
  () => {
    console.log(this.state.count)
  }
)
console.log(this.state.count) // 此处无法获取最新值

Promise + async

jsx
setStateSync(res){
   return new Promise((resolve)=>{
      this.setState(state,resolve)
   })
}

钩子函数

componentDidMount() :挂载

componentWillUnmount() :卸载

jsx
class Clock extends React.Component {
  constructor(props) {
    super(props)
    this.state = { date: new Date() }
  }
  componentDidMount() {
    this.timerID = setInterval(() => this.tick(), 1000)
  }
  componentWillUnmount() {
    clearInterval(this.timerID)
  }
  tick() {
    this.setState({ date: new Date() })
  }
  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>现在是 {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    )
  }
}

ReactDOM.render(<Clock />, document.getElementById('example'))

代码执行

  1. 调用 Clock 组件的构造函数。 初始化 this.state
  2. 调用 Clock 组件的 render() 方法。更新 DOM 以匹配 Clock 的渲染输出。
  3. Clock 的输出插入到 DOM 中时,React 调用 componentDidMount() 生命周期钩子,触发定时器。
  4. 浏览器每秒钟调用 tick() 方法。 调用 setState() ,React 知道状态已经改变,调度 UI 更新,并再次调用 render() 方法。
  5. Clock 组件被从 DOM 中移除,React 会调用 componentWillUnmount() 这个钩子函数,定时器清除。

父组件或子组件都不能知道某个组件是有状态还是无状态,并且不应该关心某组件是被定义为一个函数还是一个类。 除了拥有并设置它的组件外,其它组件不可访问

自顶向下或单向数据流: 任何状态始终由某些特定组件所有,并且从该状态导出的任何数据或 UI 只能影响树中下方的组件。

Props

纯函数:不会尝试更改入参,多次调用下相同的入参始终返回相同的结果

所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为*单个对象(props)*传递给组件

stateprops 主要区别: props 是不可变.

jsx
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>
}
const element = <Welcome name="Sara" />
ReactDOM.render(element, document.getElementById('root'))

默认 Props

组件类的 defaultProps 属性为 props 设置默认值

jsx
class HelloMessage extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

HelloMessage.defaultProps = {
  name: 'Runoob'
}

通常在父组件中设置 state, 并通过在子组件上使用 props 将其传递到子组件上。

jsx
class WebSite extends React.Component {
  constructor() {
    super()
    this.state = {
      name: '菜鸟教程',
      site: 'https://www.runoob.com'
    }
  }
  render() {
    return (
      <div>
        <Name name={this.state.name} />
        <Link site={this.state.site} />
      </div>
    )
  }
}

class Name extends React.Component {
  render() {
    return <h1>{this.props.name}</h1>
  }
}

class Link extends React.Component {
  render() {
    return <a href={this.props.site}>{this.props.site}</a>
  }
}

ReactDOM.render(<WebSite />, document.getElementById('example'))

Props 验证

Props 验证使用 propTypes:保证我们的应用组件被正确使用,类型错误会警告

React.PropTypes 在 React v15.5 版本后已经移到了 prop-types 库。

html
<script src="https://cdn.bootcss.com/prop-types/15.6.1/prop-types.js"></script>
jsx
var title = '菜鸟教程'
// var title = 123;
class MyTitle extends React.Component {
  render() {
    return <h1>Hello, {this.props.title}</h1>
  }
}

MyTitle.propTypes = {
  title: PropTypes.string
}
ReactDOM.render(<MyTitle title={title} />, document.getElementById('example'))

event

ReactHtml
事件绑定属性命名驼峰式小写
阻止默认行为e.preventDefault()
React 不能用false阻止默认行为
return false
事件监听addEventListener

事件处理

React 元素事件处理和 DOM 元素相似,语法存在以下不同

  • React 事件命名采用小驼峰式(camelCase),而不是纯小写
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
  • React 不能通过返回 false 方式阻止默认行为。你必须显式使用 preventDefault
  • React 无需使用 addEventListener 添加监听器
jsx
<!-- 传统HTML -->
<button onclick="activateLasers()">
  Activate Lasers
</button>
<!-- React -->
<button onClick={activateLasers}>
  Activate Lasers
</button>
jsx
<!-- 传统HTML -->
<a href="#" onclick="console.log('The link was clicked.'); return false">
  Click me
</a>
<!-- React -->
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }
  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

JavaScript 中,class 的方法默认不会绑定 this。使用 bind函数绑定

jsx
class Toggle extends React.Component {
  constructor(props) {
    super(props)
    this.state = { isToggleOn: true }

    // 这边绑定是必要的,这样 `this` 才能在回调函数中使用
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    this.setState((prevState) => ({
      isToggleOn: !prevState.isToggleOn
    }))
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.isToggleOn ? 'ON' : 'OFF'}</button>
  }
}

ReactDOM.render(<Toggle />, document.getElementById('example'))

使用实验性的 public class fields语法,可以直接添加箭头函数可以省略 bind

Create React App 默认启用此语法

jsx
class LoggingButton extends React.Component {
  // 确保了 `this` 绑定在  handleClick 中
  handleClick = () => {
    console.log('this is:', this)
  }
}

没有使用 class fields 语法,在回调中使用箭头函数

jsx
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this)
  }
  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。
    return <button onClick={() => this.handleClick()}>Click me</button>
  }
}

问回调函数作为一个属性值传入低阶组件,组件可能会进行额外的重新渲染

建议构造函数中使用bindpublic class fields语法

jsx
class LoggingButton extends React.Component {
  render() {
    //  这个语法确保了 `this` 绑定在  handleClick 中
    return <button onClick={(e) => this.handleClick(e)}>Click me</button>
  }
}

事件传参

箭头函数:事件参数必须显示传递。

bind函数:事件对象是隐式传递。事件对象e排在所有参数之后

jsx
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

条件渲染

if else

jsx
function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <UserGreeting />
  }
  return <GuestGreeting />
}
ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('example')
)

与运算符 &&

true && expression 总是会返回 expression, 而 false && expression 总是会返回 false

jsx
function Mailbox(props) {
  const unreadMessages = props.unreadMessages
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 && <h2>You have {unreadMessages.length} unread messages.</h2>}
    </div>
  )
}
const messages = ['React', 'Re: React', 'Re:Re: React']
ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'))

三目运算符

condition ? true : false

jsx
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      {isLoggedIn
        ? <LogoutButton onClick={this.handleLogoutClick} />
        : <LoginButton onClick={this.handleLoginClick} />
      }
    </div>
  );
}

*更复杂情况,考虑提取组件*

不满足单根,使用<React.Fragment></React.Fragment>包裹,简写为 <> </>

隐藏组件

render 方法直接返回 null,而不进行任何渲染。但componentWillUpdatecomponentDidUpdate 依然可以被调用

列表 & Keys

列表

通过使用 {} 在 JSX 内构建一个元素集合。

jsx
const numbers = [1, 2, 3, 4, 5]
const listItems = numbers.map((numbers) => <li>{numbers}</li>)

ReactDOM.render(<ul>{listItems}</ul>, document.getElementById('example'))

keys

Keys :在 DOM 中某些元素被增加或删除,帮助 React 识别哪些元素发生了变化

选择不指定显式 key 值,将默认使用索引用作为列表项目的 key 值

不建议使用索引来用作 key,因为这样做会导致性能变差,还可能引起组件状态的问题

jsx
const numbers = [1, 2, 3, 4, 5]
const listItems = numbers.map((number) => <li key={number.toString()}>{number}</li>)

key 只有放在就近的数组上下文中才有意义

jsx
function ListItem(props) {
  // 正确!这里不需要指定 key:  return <li>{props.value}</li>;}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 正确!key 应该在数组的上下文中被指定
    <ListItem key={number.toString()} value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
  <NumberList numbers={numbers} />,
  document.getElementById('root')
);

key在兄弟之间必须唯一不需要全局唯一。生成两个不同数组时,可以使用相同的 key

jsx
function Blog(props) {
  const sidebar = (
    <ul>
      {props.posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
  const content = props.posts.map((post) => (
    <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
    </div>
  ))
  return (
    <div>
      {sidebar}
      <hr />
      {content}
    </div>
  )
}

const posts = [
  { id: 1, title: 'Hello World', content: 'Welcome to learning React!' },
  { id: 2, title: 'Installation', content: 'You can install React from npm.' }
]
ReactDOM.render(<Blog posts={posts} />, document.getElementById('root'))

key不会传给组件,也就是无法通过 props.key获取

表单

React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同。

因为表单元素通常会保持一些内部 state

表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。在 React 中执行相同的代码,依然有效

受控组件

HTML:单元素(如<input><textarea><select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。

React:可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新

两者结合(受控组件),React 的 state 成为*“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作*。

受控组件:state 管理 value,非受控组件:通过操作 dom 管理组件

jsx
class NameForm extends React.Component {
  constructor(props) {
    super(props)
    this.state = { value: '' }
    this.handleChange = this.handleChange.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleChange(event) {
    this.setState({ value: event.target.value })
  }
  handleSubmit(event) {
    alert('提交的名字: ' + this.state.value)
    event.preventDefault()
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          名字:
          <input type="text" value={this.state.value} onChange={this.handleChange} />{' '}
        </label>
        <input type="submit" value="提交" />
      </form>
    )
  }
}

表单元素上设置了 value 属性,值始终为 this.state.value,使得 React 的 state 成为唯一数据源

handlechange 在每次按键时都会执行并更新 state,因此显示的值将随着用户输入而更新

textarea

HTML:通过其子元素定义其文本

jsx
<textarea>文本</textarea>

React<textarea> 使用 value 属性代替

jsx
class EssayForm extends React.Component {
  /*
  	...
  */
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          文章:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="提交" />
      </form>
    )
  }
}

select

HTML:通过 selected属性选中元素

jsx
<select>
  <option value="grapefruit">葡萄柚</option>
  <option value="lime">酸橙</option>
  <option selected value="coconut">
    椰子
  </option>
  <option value="mango">芒果</option>
</select>

React:根 select 标签上使用 value 属性

jsx
class FlavorForm extends React.Component {
  /*
  	...
  */
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          选择你喜欢的风味:
          <select value={this.state.value} onChange={this.handleChange}>
            <option value="grapefruit">葡萄柚</option>
            <option value="lime">酸橙</option>
            <option value="coconut">椰子</option>
            <option value="mango">芒果</option>
          </select>
        </label>
        <input type="submit" value="提交" />
      </form>
    )
  }
}

多选:<select multiple={true} value={['B', 'C']}>

文件 input

HTML<input type="file"> 允许用户从存储设备中选择一个或多个文件,将其上传到服务器,或通过使用 JavaScript 的 File API 进行控制。

处理多个输入

处理多个 input 元素时,我们可以给每个元素添加 name 属性,根据 event.target.name 的值选择要执行的操作

jsx
class Reservation extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    }
    this.handleInputChange = this.handleInputChange.bind(this)
  }

  handleInputChange(event) {
    const target = event.target
    const value = target.name === 'isGoing' ? target.checked : target.value
    const name = target.name
    this.setState({
      [name]: value
    })
  }

  render() {
    return (
      <form>
        <label>
          参与:
          <input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          来宾人数:
          <input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} />
        </label>
      </form>
    )
  }
}

受控输入空值

在受控组件上指定 valueprop 会阻止用户更改输入。你指定了 value,但输入仍可编辑,则可能是你意外地将value 设置为 undefinednull

代码:输入最初被锁定(只读),但在短时间延迟后变为可编辑(可写

jsx
ReactDOM.render(<input value="hi" />, mountNode)

setTimeout(function () {
  ReactDOM.render(<input value={null} />, mountNode)
}, 1000)

状态提升

多个组件需要反映相同的变化数据将共享状态提升到最近的共同父组件中去

jsx
import MyInput from './myInput'
function tryConvert(number, mode) {
  if (mode === 'default') {
    return number * 10
  } else if (mode === 'multiply') {
    return number / 10
  }
}
class ShowInput extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      number: '',
      mode: ''
    }
  }

  onDefaultChange = (number) => {
    this.setState({
      mode: 'default',
      number
    })
  }
  onMultiplyChange = (number) => {
    this.setState({
      mode: 'multiply',
      number
    })
  }

  render() {
    const { mode, number } = this.state
    const a = mode === 'default' ? number : tryConvert(number, mode)
    const b = mode === 'multiply' ? number : tryConvert(number, mode)
    return (
      <div>
        <MyInput mode="default" number={a || ''} onNumberChange={this.onDefaultChange}></MyInput>
        <MyInput mode="multiply" number={b || ''} onNumberChange={this.onMultiplyChange}></MyInput>
      </div>
    )
  }
}

export default ShowInput
jsx
const modeNames = {
  default: '*1',
  multiply: '*10'
}
class MyInput extends React.Component {
  constructor(props) {
    super(props)
  }
  numberChange = (e) => {
    this.props.onNumberChange(e.target.value)
  }
  render() {
    const { mode, number } = this.props
    return (
      <fieldset>
        <legend>
          {number} {modeNames[mode]}
        </legend>
        <input value={number} onChange={this.numberChange} />
      </fieldset>
    )
  }
}
export default MyInput

组合 vs 继承

包含关系 - slot

无法提前知晓它们子组件的具体内容

组件预留一个children prop 来将他们的子组件传递到渲染结果。通过 JSX 嵌套,将任意组件作为子组件传递给它们。

jsx
function FancyBorder(props) {
  return <div className={'FancyBorder FancyBorder-' + props.color}>{props.children}</div>
}
jsx
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">Welcome</h1>
      <p className="Dialog-message">Thank you for visiting our spacecraft!</p>
    </FancyBorder>
  )
}

组件预留多个,将所需内容传入 props,并使用相应的 prop。

jsx
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">{props.left}</div>
      <div className="SplitPane-right">{props.right}</div>
    </div>
  )
}

function App() {
  return <SplitPane left={<Contacts />} right={<Chat />} />
}

组件间复用非 UI 的功能,将其提取为一个单独的 JavaScript 模块

React 哲学

UI 划分组件层级

一个组件原则上只能负责一个功能。需要负责更多的功能,这时候就应该考虑将它拆分成更小的组件

  • FilterableProductTable 产品列表
    • SearchBar - 搜索框
    • ProductTable - 表单
      • ProductCategoryRow - 产品分类
      • ProductRow - 产品

创建一个静态版本

应用静态版本:要编写大量代码,而不需要考虑太多交互细节

添加交互功能:考虑大量细节,而不需要编写太多代码。

通过 props 传入所需的数据,不应该使用 state 构建静态版本,state 代表了随时间会产生变化的数据

确定 UI state 最小(且完整)表示

要使你的 UI 具备交互功能,需要有触发基础数据模型改变的能力,通过state来完成。

只保留所需最小state集合,其他数据由计算产生

样例分析

  • 包含所有产品的原始列表
  • 用户输入的搜索词 - state
  • 复选框是否选中的值 - state
  • 经过搜索筛选的产品列表

判断规则 - 以下不是 state

  • 是否由父组件 props传递?
  • 随时间推移保持不变?
  • 是否根据其他stateprops计算

确定 state 放置位置

需要确定哪个组件能够改变这些 state,或者拥有这些 state

筛选规则

  • 找到根据这个 state 进行渲染的所有组件
  • 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
  • 共同所有者组件或者比它层级更高的组件应该拥有该 state
  • 找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置

FilterableProductTable 产品列表

  • SearchBar - 搜索框 - 展示 复选框 和 搜索词
  • ProductTable - 表单 - 根据 props 进行筛选
    • ProductCategoryRow - 产品分类
    • ProductRow - 产品

SearchBarproductTable 共同所有者 为 FilterableProductTable(存放 state)

添加反向数据流

尝试让数据反向传递:处于较低层级的表单组件更新较高层级FilterableProductTable 中的 state

state 只能由拥有它们的组件进行更改。FilterableProductTable需要向 SearchBar传入触发 state 改变的回调,当SearchBar 改变,触发此回调。

组件生命周期

三个状态

Mounting已插入真实 DOM

Updating:正在被重新渲染

Unmounting已移出真实 DOM

周期方法

方法说明
componentWillMount渲染前调用,在客户端也在服务端
componentDidMount第一次渲染后调用,只在客户端在此之后可以通过 this.getDOMNode()来进行访问
componentWillReceiveProps组件接收到新 prop (更新后)时被调用。*初始化render*时不会被调用
shouldComponentUpdate在组件接收到新props或者state被调用返回boolean值,初始化或使用forceUpdate时不被调用,true代表允许改变
componentWillUpdate组件接*收到新props或者state但**还没有 render***时被调用初始化时不会被调用
componentDidUpdate组件完成更新后立即调用在初始化时不会被调用。
componentWillUnmount组件从 DOM 中移除之前立刻被调用