一、Hello World
<!DOCTYPE html> <html lang="en"> <head> <Meta charset="UTF-8"> <title>React demo</title> <script src="../build/react.js"></script> <script src="../build/react-dom.js"></script> <script src="../build/browser.min.js"></script> </head> <body> <div id="root"></div> <script type="text/babel"> function Hello() { return <h1>Hello world.</h1>; } ReactDOM.render( <Hello/>,document.getElementById(‘root‘) ) </script> </body> </html>
二、JSX 简介
JSX 是一种 JavaScript 的语法扩展。推荐在 React 中使用 JSX 来描述用户界面,JSX 用来声明 React 当中的元素。
2.1 在 JSX 中使用表达式
在 JSX 当中的表达式要包含在大括号里。
const element = ( <h1> Hello,{formatName(user)}! </h1> );
推荐在 JSX 代码的外面括上小括号,这样可以防止分号自动插入的 BUG。
JSX 本身其实也是一种表达式
在编译之后,JSX 其实会被转化为普通的 JavaScript 对象。
JSX 属性
const element = <img src={user.avatarUrl}></img>;
使用了大括号包裹的 JavaScript 表达式时就不要再到外面套引号了。 JSX 会将引号当中的内容识别为字符串而不是表达式。
JSX 嵌套
如果 JSX 标签是闭合式的,那么你需要在结尾处用/>,就好像 XML/HTML 一样:
const element = <img src={user.avatarUrl}/>;
JSX 标签同样可以相互嵌套:
const element = ( <div> <h1>Hello!</h1> <h2>Good to see you here.</h2> </div> )
警告:
因为 JSX 的特性更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase 小驼峰命名来定义属性的名称,而不是使用 HTML 的属性名称。
例如,class 变成了 className,而 tabindex 则对应着 tabIndex.
JSX 代表 Objects
Babel 转译器会把 JSX 转换成一个名为 React.createElement() 的方法调用。
下面两种代码的作用是完全相同的:
const element = ( <h1 className="greeting"> Hello,world! </h1> );
const element = React.createElement( ‘h1‘,{className: ‘greeting‘},‘Hello,world!‘ );
React.createElement() 这个方法首先会进行一些避免 bug 的检查,之后返回一个类似下面例子的对象:
// 注意: 以下示例是简化过的(不代表在 React 源码中是这样) const element = { type: ‘h1‘,props: { className: ‘greeting‘,children: ‘Hello,world‘ } };
这样的对象被称为“React 元素”。
三、元素渲染
元素是构成 React 应用的最小单位。
3.1 将元素渲染到 DOM 中
首先定义一个 id 为 root 的 div:
<div id=‘root‘></div>
在此 div 中的所有内容都将由 React DOM 来管理,所以我们将其称之为“根” DOM 节点。
我们用 React 开发应用时一般只会定义一个根节点。
要将 React 元素渲染到根 DOM 节点中,我们通过把它们都传递给 ReactDOM.render() 的方法来将其渲染到页面上:
const element = <h1>Hello,world</h1>; ReactDOM.render( element,document.getElementById(‘root‘) )
四、组件 & Props
组件可以将 UI 切分为一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件。
组件从概念上看就像是函数,它可以接收任意的输入值(称之为“props”),并返回一个需要在页面上展示的 React 元素。
4.1 函数定义/类定义组件
定义一个组件最简单的方式是使用 JavaScript 函数:
// 函数定义组件 function Welcome(props){ return <h1>Helle,{props.name}</h1>; }
也可以使用 ES6 class 来定义一个组件:
拓展:
ES6 是什么?
ES6:全称 ECMScript 6.0。是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布。
// 类定义组件 class Welcome extents React.Component { render(){ return <h1>Hello,{this.props.name}</h1>; } }
4.2 组件渲染
第一步,定义一个 Welcome 组件:
function Welcome(props) { return <h1>Hello,{props.name}</h1>; }
第二步,使用该组件创建 React 元素:
const element = <Welcome name=‘kevin‘/>;
第三步,使用 ReactDOM.render() 方法将 React 元素渲染到页面上:
ReactDOM.render( element,document.getElementById(‘root‘); )
4.3 组合组件
组件可以在它的输出中引用其他组件:
function Welcome(props) { return <h1>Hello,{props.name}</h1>; } function App() { return ( <div> <Welcome name=‘kevin‘/> <Welcome name=‘tom‘/> <Welcome name=‘bob‘/> </div> ); } ReactDOM.render( <App/>,document.getElementById(‘root‘) );
警告:
组件的返回值只能有一个根元素。
4.4 props 的只读性
props 是不可变的
五、State & 生命周期
状态与属性十分相似,但是状态是私有的,完全受控于当前组件。
使用类定义组件,就可以使用局部状态、生命周期钩子等特性。
5.1 为一个类添加局部状态
第一步:使用 ES6 class 定义一个组件
class Clock extents React.component { render() { return ( <div> <h1>Hello,world!</h1> <h2>It is {this.state.date.toLocaleTimeString}</h2> </div> ) } }
第二步:添加一个类构造函数来初始化状态 this.state
class Clock extents React.component { constructor(props) { super(props); this.state = {date : new Date()}; } render() { return ( <div> <h1>Hello,world!</h1> <h2>It is {this.state.date.toLocaleTimeString}</h2> </div> ) }; }
第三步:使用 ReactDOM.render() 方法渲染组件到页面
class Clock extents React.component { constructor(props) { super(props); this.state = {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‘) )
5.2 将生命周期方法添加到类中
在 5.1 中,我们只是添加了一个状态,那么,如何更新状态呢?答案是:使用生命周期方法。
class Clock extents React.component { constructor(props) { super(props); this.state = {date : new Date()}; } componentDidMount() { this.timeID = setInterval( () => this.tick(),1000 ); } componentWillUnmount() { clearInterval(this.timeID); } tick(){ this.setState({ date : new Date() }); } render() { return ( <div> <h1>Hello,document.getElementById(‘root‘) )
当 Clock 的输出插入到 DOM 中时,React 会调用 componentDidMount() 生命周期方法。上面代码示例中,我们在该方法中设置了一个定时器,每个 1 秒钟调用一次 tick() 方法。
在 tick() 方法中,Clock 组件通过使用包含当前时间的对象调用 setState() 来更新状态。当 React 发现状态已经更新时,会再次调用 ReactDOM.render() 方法更新 DOM。
当 Clock 组件被从 DOM 中移除,React 会调用 componentWillUnmount() 生命周期方法。
5.3 正确地使用状态
- 不要直接更新状态
构造函数是唯一能够初始化 this.state 的地方。其他地方更薪状态只能使用 this.setState();
- 状态更新可能是异步的
不太懂 TODO
- 状态更新合并
不太懂 TODO
5.4 数据自顶向下流动
父组件或子组件都不能知道某个组件是有状态还是无状态,并且它们不应该关心某组件是被定义为一个函数还是一个类。
所以状态通常被成为局部或封装。除了拥有并设置它的组件外,其他组件不可访问。
但组件可以选择将其状态作为属性传递给其子组件。
这通常被称为 自顶向下 或 单向 数据流。任何状态始终由某些特定组件所有,并且该状态导出的任何数据或 UI 只能影响树中 下方 的组件。
六、事件处理
例如,传统的 HTML 写法:
<button onclick="activateLasers()"> Activate Lasers </button>
React 写法:
<button onClick={activateLasers}> Activate Lasers </button>
- 在 React 中,不能使用返回 false 的方式阻止默认行为。你必须明确的使用 preventDefault。
6.1 开关实例
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(‘root‘) )
6.2 向事件处理程序传递参数
有两种方式向事件处理程序传递参数,分别是:
- arrow function:箭头函数
<button onClick={(e) => this.deleleRow(id,e)}>Delete Row</button>
- Function.prototype.bind:绑定
<button onClick={this.deleleRow.bind(this,id)}>Delete Row</button>
参数 e 作为 React 的事件对象将会被作为第二个参数进行传递。
箭头函数的方式,事件对象必须显式传递;
绑定的方式,事件对象以及更多的参数将会被隐式传递。值得注意的是,通过绑定的方式向监听函数传参,在类组件中定义的监听组件,事件对象 e 排在所传递参数的后面。
七、条件渲染
React 中的条件和 JavaScript 中的一致,使用 JavaScript 操作符 if 或 条件运算符 来创建表示当前状态的元素,然后让 React 来更新 UI。
7.1 if
示例:
function UserGreeting() { return <h1>Welcome back!</h1>; } function GuestGreeting() { return <h1>Please sign up.</h1> } function Greeting(props) { const isLoggedIn = props.isLoggedIn; if (isLoggedIn){ return <UserGreeting/> } return <GuestGreeting/> } ReactDOM.render( <Greeting isLoggedIn={true}/>,document.getElementById(‘root‘) )
7.2 与运算符 &&
function MailBox(props) { const message = props.message; return ( <div> <h1>Hello!</h1> {message.length > 0 && <h2> You have {message.length} unread message. </h2>} </div> ) } const message = [‘React‘,‘Re:React‘,‘Re:Re:React‘]; ReactDOM.render( <MailBox message={message}/>,document.getElementById(‘root‘) )
true && expression 总是返回 expression,而 false && expression,React 会忽略并跳过它。
7.3 三目运算符
class Login extends React.Component { constructor(props) { super(props); this.state = {isLogin: false}; this.login = this.login.bind(this); } login() { this.setState( prevState => ({ isLogin: !prevState.isLogin }) ); } render (){ return ( <div> <h1> {this.state.isLogin ? ‘Welcome‘ : ‘Please Login‘} </h1> <button onClick={this.login}></button> </div> ); } } ReactDOM.render( <Login/>,document.getElementById(‘root‘) )
7.4 阻止组件渲染
让组件的 render 方法返回 null 即可阻止组件渲染。
八、列表 & Keys
function ToDoList(props) { const things = props.things; const todolist = things.map((thing) => <li key={thing.toString()}>{thing}</li> ); return ( <ul>{todolist}</ul> ) } const things = [1,2,3,4,5]; ReactDOM.render( <ToDoList things={things}/>,document.getElementById(‘root‘) )
- 使用 map() 函数遍历数组
- 应当给数组中的每一个元素赋予一个确定的标识(key)。这样,当某些元素被增加或删除时能帮助 React 识别哪些元素发生了变化。
- 元素的 key 只有在它和它的兄弟节点对比时才有意义。
- 元素的 key 在其兄弟元素之间应该唯一。
九、表单
9.1 受控组件
在HTML当中,像
<input>
、<textarea>
和<select>
这类表单元素会维持自身状态,并根据用户输入进行更新。但在 React 中,可变的状态通常保存在组件的状态属性中,并且只能用 setState() 方法进行更新。
我们通过使 React 变成一种单一数据源的状态来结合二者。React 负责渲染表单的组件仍然控制用户后续输入时所发生的变化。相应的,其值由 React 控制的输入表单元素称为“受控组件”。
class Form extends React.Component { constructor(props) { super(props); this.state = {value : ‘1‘} this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange (event) { this.setState({ value : event.target.value }) } handleSubmit (event) { alert(‘name is ‘ + this.state.value); event.preventDefault(); } render() { return ( <form onSubmit={this.handleSubmit}> <label> Name: <select value={this.state.value} onChange={this.handleChange}> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> <option value="4">4</option> </select> </label> <input type="submit" value="Submit"></input> </form> ) }; } ReactDOM.render( <Form/>,document.getElementById(‘root‘) )
<input type=‘text‘>
、<textarea>
、<select>
类似,都是通过传入一个 value
属性来实现对组件的控制。
9.2 多个输入框
class Form extends React.Component { constructor(props) { super(props); this.state = { name : ‘‘,age: 0 } this.handleChange = this.handleChange.bind(this); this.handleSubmit = this.handleSubmit.bind(this); } handleChange(event) { const target = event.target; const name = target.name; const value = target.value; this.setState({ [name] : value }) } handleSubmit(event) { alert(‘name is ‘ + this.state.name + ‘,and age is ‘ + this.state.age); event.preventDefault(); } render () { return ( <form onSubmit={this.handleSubmit}> <label> Name: <input type="text" name="name" value={this.state.name} onChange={this.handleChange}/> </label> <label> age: <input type="text" name="age" value={this.state.age} onChange={this.handleChange}/> </label> <input type="submit" value="Submit"/> </form> ) } } ReactDOM.render( <Form/>,document.getElementById(‘root‘) )
当有多个受控的元素时,可以给每个元素添加一个 name 属性,来让处理函数 event.target.name 的值区分。