JSX简介

JSX是一种JavaScript的语法扩展,可以在大括号内中使用任何JavaScript表达式。
推荐在JSX代码的外面扩上一个小括号,这样可以防止分号自动插入的bug。
可以使用引号来定义以字符串为值的属性,也可以使用大括号定义以JavaScript表达式为值的属性。
闭合标签需要在结尾处用/>标记。
使用小驼峰命名来定义属性的名称。
更多ES6的语法介绍,请参考下面内容

元素渲染

元素是构成React应用的最小单位。
const element = <h1>Hello, world</h1>;
将页面渲染到DOM中:
  • 在一个HTML页面中添加一个 id="root" 的 <div>:
<div id="root"></div>
  • 将React元素渲染到根DOM结点中:
const element = <h1>Hello, world</h1>; ReactDOM.render(element, document.getElementById('root'));
React元素都是immutable不可变的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子。更新界面的唯一办法是创建一个新的元素,然后将它传入 ReactDOM.render() 方法。

组件&Props

组件从概念上看就像是函数,它可以接收任意的输入值(称之为"props"),并返回一个需要在页面上展示的React元素。
使用函数方式定义组件:
function Welcome(props) { return <h1>Hello, {props.name}</h1>; }
使用类方式定义组件:
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
组件名称必须以大写字母开头。
组件的返回值只能有一个根元素。
所有的React组件必须像纯函数那样使用它们的props。

State & 生命周期

 
notion image
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>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } } ReactDOM.render( <Clock />, document.getElementById('root') );
创建React组件类,需要类继承自React.Component类,并且重写其中的render()方法即可。
类通过props接收外部状态,通过state保存内部状态。
只有构造函数中才可以直接修改this.state的值,其余地方想修改state,需要调用this.setState(),否则界面不会动态更新。
state和props的更新是异步的,React内部可能将多个setState()调用合并成一个调用来提高性能,如果想获取上一次的state和props,需要传递一个函数给setState()方法,调用方式如下:
this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
React中的数据都是自顶向下的单向数据流,父组件或子组件都不知道某个组件是有状态的还是无状态的,组件可以将其状态作为属性传递给其子组件。任何状态始终由某些特定的组件所有,并且改状态的任何数据或UI只能影响树中下方的组件。

事件处理

React元素的事件处理和DOM元素的事件处理区别:
  • React事件绑定属性的命名采用驼峰式写法,而不是小写。
  • 如果采用JSX的语法你需要传入一个函数作为事件处理函数,而不是一个字符串。
  • React中不能使用返回false的方式阻止默认行为,而必须明确的调用preventDefault()
class Toggle extends React.Component { constructor(props) { super(props); this.state = {isToggleOn: true}; // This binding is necessary to make `this` work in the callback 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('root') );
类的方法默认是不会绑定this的,对于回调函数的this问题,可以参考文章
this.handleClick.bind实际为Function.prototype.bind(),它会创建一个新的方法,新方法内部会设置this环境。
另外两种绑定this的方式:
  • 属性初始化器,即把回调函数handleClick当作类的属性来初始化,而不是成员函数。
class LoggingButton extends React.Component { // This syntax ensures `this` is bound within handleClick. // Warning: this is *experimental* syntax. handleClick = () => { console.log('this is:', this); } render() { return ( <button onClick={this.handleClick}> Click me </button> ); } }
  • 箭头函数,箭头函数没有自己的this,箭头函数在解析阶段会绑定父作用域的this
class LoggingButton extends React.Component { handleClick() { console.log('this is:', this); } render() { // This syntax ensures `this` is bound within handleClick return ( <button onClick={(e) => this.handleClick(e)}> Click me </button> ); } }
向事件处理程序传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button> <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
通过箭头函数的方式,事件对象必须显示的进行传递,但是通过bind方式,事件对象会被隐式传递,并且是作为最后一个参数被传递。

列表&Keys

可以通过使用{}在JSX内构建一个元素集合。
Keys帮助React识别哪些元素发生了变化,从而进行动态更新,需要在其兄弟之间是唯一的,并且只有在其环绕数组的上下文中才有意义。Keys不会作为props的属性传递给子组件。

表单

在HTML中,像<input>, <textarea><select>这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。其值由React控制的输入表单元素称为“受控组件”。
当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择做什么。
要编写一个非受控组件,而非为每个状态更新编写事件处理程序,你可以 使用 ref 从 DOM 获取表单值。

状态提升

在React中,状态分享是通过将state数据提升至离需要这些数据的组件最近的父组件来完成的。这就是所谓的状态提升
在React应用中,对应任何可变数据理应只有一个单一“数据源”。通常,状态都是首先添加在需要渲染数据的组件中。然后,如果另一个组件也需要这些数据,你可以将数据提升至离它们最近的共同祖先中。你应该依赖自上而下的数据流,而不是尝试在不同组件中同步状态。

组合vs继承

建议使用组合而不是继承来复用组件之间的代码。可以使用children属性将子元素直接传递到输出。

深入JSX

本质上来讲,JSX 只是为 React.createElement(component, props, ...children) 方法提供的语法糖。
用户定义组件必须首字母大写。
JSX的属性
  • 使用JavaScript表达式作为属性;
  • 字符串常量;
  • 属性默认为"True";
  • 可以使用...作为展开符来传递整个属性对象;
JSX中的子代
在既包含开始标签又包含结束标签的 JSX 表达式中,这两个标签之间的内容被传递为专门的属性:props.children
  • 字符串字面量作为子代;
  • JSX子代;
  • JavaScript表达式作为子代;
  • 函数作为子代;
  • 布尔值、Null 和 Undefined 被忽略(如果你想让类似 falsetruenull undefined 出现在输出中,必须先把它转换成字符串);

路由(0.13.x版本)

路由配置

相对路径:不以/开头的路径;绝对路径:以/开头的路径。
两种配置方式(5.0版本配置文件请参考文档):
import { Redirect } from 'react-router' React.render(( <Router> <Route path="/" component={App}> <IndexRoute component={Dashboard} /> <Route path="about" component={About} /> <Route path="inbox" component={Inbox}> <Route path="/messages/:id" component={Message} /> {/* 跳转 /inbox/messages/:id 到 /messages/:id */} <Redirect from="messages/:id" to="/messages/:id" /> </Route> </Route> </Router> ), document.body)
const routeConfig = [ { path: '/', component: App, indexRoute: { component: Dashboard }, childRoutes: [ { path: 'about', component: About }, { path: 'inbox', component: Inbox, childRoutes: [ { path: '/messages/:id', component: Message }, { path: 'messages/:id', onEnter: function (nextState, replaceState) { replaceState(null, '/messages/' + nextState.params.id) } } ] } ] } ] React.render(<Router routes={routeConfig} />, document.body)
Route可以定义onEnteronLeave两个hook,这些hook会在页面跳转确认时触发一次。
在路由跳转过程中,onLeave hook 会在所有将离开的路由中触发,从最下层的子路由开始直到最外层父路由结束。然后onEnter hook会从最外层的父路由开始直到最下层子路由结束。

路由匹配原理

路由拥有三个属性来决定是否匹配一个URL:
  • 嵌套关系
    • 当一个给定的URL被调用时,整个集合中命中部分都会被渲染。
  • 路径语法
    • :paramName - 匹配一段位于 /、? 或 # 之后的 URL。 命中的部分将被作为一个参数;
    • () - 在它内部的内容被认为是可选的;
    • * - 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat 参数。
    • <Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan <Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan <Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg
  • 优先级
    • 路由算法会根据定义的顺序自顶向下匹配路由。

Histories

React Router是建立在history之上的。history可以监听浏览器地址栏的变化,并解析这个URL转化为location对象,然后router使用它匹配到路由,最后正确的渲染对应的组件。
常见的history有三种,但是你也可以使用 React Router 实现自定义的 history。
  • browserHistory
    • 推荐的模式,它使用浏览器的History API用于处理URL,创建一个像example.com/some/path这样真实的 URL。对于不支持window.historyAPI的浏览器,任何调用调转的应用就会导致全页面刷新。
  • hashHistory
    • Hash history 使用 URL 中的 hash(#)部分去创建形如 example.com/#/some/path 的路由,不推荐在实际线上环境中使用。
  • createMemoryHistory
    • Memory history 不会在地址栏被操作或读取,必须显示的创建它。
      const history = createMemoryHistory(location)
使用实例:
import React from 'react' import { render } from 'react-dom' import { browserHistory, Router, Route, IndexRoute } from 'react-router' import App from '../components/App' import Home from '../components/Home' import About from '../components/About' import Features from '../components/Features' render( <Router history={browserHistory}> <Route path='/' component={App}> <IndexRoute component={Home} /> <Route path='about' component={About} /> <Route path='features' component={Features} /> </Route> </Router>, document.getElementById('app') )

IndexRoute与IndexLinks

<Router> <Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="accounts" component={Accounts}/> <Route path="statements" component={Statements}/> </Route> </Router>
如果在app中使用<Link to="/">Home</Link>,它会一直处于激活状态,因为所有的URL开头都是/,如果需要在Home路由被渲染后才激活指向/的链接,请使用<IndexLink to="/">Home</IndexLink>

动态路由

程序应当只加载当前渲染页所需的JavaScript,将代码分拆成多个小包,在用户浏览过程中按需加载。
React Router里的路径匹配以及组件加载都是异步完成的,不仅允许你延迟加载组件,而且还可以延迟加载路由配置。
Route 可以定义 getChildRoutesgetIndexRoute getComponents 这几个函数。它们都是异步执行,并且只有在需要时才被调用。 React Router 会逐渐的匹配 URL 并只加载该 URL 对应页面所需的路径配置和组件。
const CourseRoute = { path: 'course/:courseId', getChildRoutes(location, callback) { require.ensure([], function (require) { callback(null, [ require('./routes/Announcements'), require('./routes/Assignments'), require('./routes/Grades'), ]) }) }, getIndexRoute(location, callback) { require.ensure([], function (require) { callback(null, require('./components/Index')) }) }, getComponents(location, callback) { require.ensure([], function (require) { callback(null, require('./components/Course')) }) } }

跳转前确认

React Router 提供一个 routerWillLeave 生命周期钩子,这使得 React 组件可以拦截正在发生的跳转,或在离开 route 前提示用户。routerWillLeave 返回值有以下两种:
  1. return false 取消此次跳转;
  1. return 返回提示信息,在离开 route 前提示用户进行确认。

服务端渲染

与客户端渲染区别:
  • 发生错误时发送一个 500 的响应
  • 需要重定向时发送一个 30x 的响应
  • 在渲染之前获得数据 (用 router 帮你完成这点)
为了迎合这一需求,你要在 <Router> API 下一层使用:
  • 使用 match 在渲染之前根据 location 匹配 route
  • 使用 RoutingContext 同步渲染 route 组件
import { renderToString } from 'react-dom/server' import { match, RoutingContext } from 'react-router' import routes from './routes' serve((req, res) => { // 注意!这里的 req.url 应该是从初始请求中获得的 // 完整的 URL 路径,包括查询字符串。 match({ routes, location: req.url }, (error, redirectLocation, renderProps) => { if (error) { res.send(500, error.message) } else if (redirectLocation) { res.redirect(302, redirectLocation.pathname + redirectLocation.search) } else if (renderProps) { res.send(200, renderToString(<RoutingContext {...renderProps} />)) } else { res.send(404, 'Not found') } }) })

组件生命周期

路由配置如下:
<Route path="/" component={App}> <IndexRoute component={Home}/> <Route path="invoices/:invoiceId" component={Invoice}/> <Route path="accounts/:accountId" component={Account}/> </Route>
用户打开应用'/'页面
App: componentDidMount
Home: componentDidMount
Invoice: N/A
Account: N/A
 
用户从'/'跳转到'/invoice/123'
App: componentWillReceiveProps ⇒ componentDidUpdate
Home: componentWillUnmount
Invoice: componentDidMount
Account: N/A
 
用户从'/invoice/123'跳转到'/invoice/456'
App: componentWillReceiveProps ⇒ componentDidUpdate
Home: N/A
Invoice: componentWillReceiveProps ⇒ componentDidUpdate
Account: N/A
 
用户从 '/invoice/789' 跳转到 '/accounts/123'
App: componentWillReceiveProps ⇒ componentDidUpdate
Home: N/A
Invoice: componentWillUnmount
Account: componentDidMount

组件外部使用导航

使用history的API操作导航:
  • history.push(path, [state])
  • history.replace(path, [state])
  • history.go(n)
  • history.goBack()
  • history.goForward()
  • history.canGo(n) (only in createMemoryHistory)

获得上一次路径

<Route component={App}> {/* ... 其它 route */} </Route> const App = React.createClass({ getInitialState() { return { showBackButton: false } }, componentWillReceiveProps(nextProps) { const routeChanged = nextProps.location !== this.props.location this.setState({ showBackButton: routeChanged }) } })
Hooks
Context
badge