react-learning

JSX

可以当做是JavaScript对象,可以对其进行赋值或者设置属性

1
2
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;

这种方法符合es6的语法,同样可以调用React.createElement()的方法,类似构建虚拟dom

1
2
3
4
5
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);

也可以在JSX当中使用JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
var names = ['Alice', 'Emily', 'Kate'];

ReactDOM.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
);

注意里面是使用大括号隔开

渲染元素

react应用会在html当中寻找<div id="root"></div>的根节点,并在其上搭建react应用,如果要在react嵌入已有的应用中,这样的根节点可以有多个

可以将dom节点和这个元素一个传进ReactDOM.render()当中进行渲染

1
2
3
4
5
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);

react元素是不变的,更新页面的方法是创建一个新的元素,并给ReactDOM.render()渲染并覆盖原有元素,react内部机制会比较这个dom节点和他的孩子当中不相同的地方并更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);

组件

react当中的组件可以是函数式的组件,只要能够接受一个prop属性,然后返回一个react元素,用户定义的组件的名称的第一个字母需要大写,注意组件返回的模板里面只能有一个子节点

1
2
3
4
5
6
7
8
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);

上述例子当中,当react看到用户定义的组件,便会将JSX输出传到组件当中(这个组件是一个函数对象),同时成为这个函数对象的props属性,通过大括号引用进去,之后再进行元素的渲染

注意react组件应该是纯函数,即对传进来的props值不能进行修改,纯函数的定义是对传进来的参数不能进行修改,同时传进相同的参数应该返回相同的结果

状态和生命周期

react组件同样可以使用类来定义,其中类有一个特殊的属性:state,是类的私有成员

将function组件转化成类组件

  • React.Component当中扩展一个类
  • function定义当中的props改为this.state
  • function函数体复制到类的方法render()当中
  • 在这个类的构造函数当中初始化state的值
  • 将props的值传给基构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Clock extends 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')
);

代码当中ReactDOM.render同样是渲染这个组件,但是提日期的值被封装起来

在类当中添加生命周期

如果要完成对组件内部数据的更新,可以在生命周期函数当中定义函数并更改,不违反组件设计的原则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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')
);

通过将一个state对象传入setState函数当中,来修改视图的显示

注意

  • 除了在constructor函数当中,不能直接的修改state的值,必须通过setState方法,(类似vuex)
  • state的变化有可能是异步更新的,所以我们在setState当中传递的对象不能使用原有stateprops的值

解决方法:

1
2
3
4
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));

setState函数接受一个prevState表示state更新之前的状态,props表示视图已经更新之后的值

事件处理

1
2
3
4
5
6
7
8
9
10
11
12
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}

注意语法

  • 要使用e.preventDefault()取消浏览器默认的事件(不能通过return false来取消)
  • 事件的语法应该采用驼峰式命名,用大括号包括
  • 在组件内定义的事件处理函数应该绑定this,不然调stateprops的时候thisundefined,使用属性初始化语法或者直接在定义函数的时候绑定this的值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
// 也可以在constructor当中
// this.handleClick = this.handleClick.bind(this);
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}

条件渲染

react可以根据props选择性的渲染组件,在if的判断的选择性的返回相应JSX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}

handleLoginClick() {
this.setState({isLoggedIn: true});
}

handleLogoutClick() {
this.setState({isLoggedIn: false});
}

render() {
const isLoggedIn = this.state.isLoggedIn;

let button = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}

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

上述代码当中,根据当前的状态this.state来判断渲染的元素,可以用一个变量a储存元素,后面使用{a}将其渲染,将其嵌入JSX当中

true && expression表达式渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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')
);

通过props的值进行判断,如果是true的话进行渲染,如果是false的话就忽略跳过

三目运算符渲染

1
2
3
4
5
6
7
8
9
10
11
12
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}

当只有字面值的时候可以不用加括号

不渲染组件

类似v-if或者ngIf,通过判断组件的props值进行渲染,在组件的当中,如果return null,那么组件将被忽略,不渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function WarningBanner(props) {
if (!props.warn) {
return null;
}

return (
<div className="warning">
Warning!
</div>
);
}

class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true}
this.handleToggleClick = this.handleToggleClick.bind(this);
}

handleToggleClick() {
this.setState(prevState => ({
showWarning: !prevState.showWarning
}));
}

render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}

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

但在这种情况下,组件的生命周期函数依旧会被调用,如componentWillUpdatecomponentDidUpdate

列表和数组操作

可以批量创建元素并且将他们用大括号包裹在JSX当中,例如ulli

1
2
3
4
5
6
7
8
9
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);

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

键值

在组件当中动态创建元素,为了提高插入和删除的效率,会使用键值标注元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);

// 这种方法在重新排序的数组里面效率会较慢

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

// 一般使用数组带有的数据表示

在结构组件当中使用键值

键值的设置应该是在模板的<listItems\>而不是在组件定义的<li>当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}

function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<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是隐式传递到组件当中,所以无法在使用props.key进行引用,可以用一个另外的属性标注,例如id

表单处理

为了使用表单的提交功能,可以使用控制组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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('A name was submitted: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

注意代码当中,对用户输入的值value声明为state,并同事绑定事件处理函数,value的值一旦发生改变(即用户输入),就会触发函数对state的值进行修改,这样就不会违背react组件数据不变的性质

select框处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({value: event.target.value});
}

handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

通过封装这个选择的组件,设定value的值来确定显示的数据,对于数据的更改同样通过绑定Onchange时间来对state进行更改

多个input控件

可以通过在input当中设置name的属性,之后在handleChange函数里面根据e.target.name进行条件判断进行更改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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.type === 'checkbox' ? target.checked : target.value;
const name = target.name;

this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}

状态提升

react当中要实现状态的共享和同步,必须把组件内部的state状态提升到父组件的状态,并通过props组件在子组件当中传递,同时props的修改,可以类似定义valueonChange类似的控制组件来实现数据的更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}

render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}

注意代码当中模板里面value的改变触发了handleChange函数,这个函数里面再调用父组件props属性里面改变父组件state的方法,实现数据的更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}

父组件当中模板的定义,当用户对输入框当中输入数字

  1. 组件TemperatureInput中的inputvalue更改,触发handleChange函数
  2. handleChange函数调用来自父组件props的方法onTemperatureChange
  3. onTemperatureChange函数已经在父组件的模板当中被传入不同的处理函数
  4. 假设我们在第一个TemperatureInput当中输入数字,那么最终触发父组件定义的handleCelsiusChange函数
  5. 函数里面对父组件里面定义的state进行修改,由于另一个TemperatureInput也是绑定了这个state,然后传递到子组件当中,修改了input组件的值

组件的内容

props.children

有点类似vue当中的slot渲染,可以将父组件当中的属性(JSX)渲染在子组件固定的位置

1
2
3
4
5
6
7
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
1
2
3
4
5
6
7
8
9
10
11
12
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}

这种形式相当于子组件是一个内部可以嵌套的盒子,将子组件嵌在父组件和父组件的内容当中,使用React.Children.map可以避免对子元素类型的不确定

props.someProperty

通过在父组件当中具体设置某个插槽(即props的值),使得这个组件更加易于定制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}

function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}

子组件可以使用抽象的props元素,它具体的值将会由父组件传入

软件设计的原则

关于state

应该遵循三个原则去设计各个组件相应的state的值,达到最小的消耗

  1. 封装性,不能是父组件传递到子组件的props
  2. 随着时间或者用户交互,这个值会产生变化
  3. 如果其他propsstate能够计算出这个值,那么这个数据不能当做state

### 关于单向数据流

react之间父子组件之间通过单向数据流进行值的传递,子组件的值往往是由父组件的state属性决定,所以通过用户交互更改子组件的值,并不会改变父组件的state,要使得子组件像父组件通信,应该将setState方法传递到子组件当中,然后通过事件处理函数调用setstate函数,就能实现子组件向父组件进行通信

代码分割

对于一个复杂的应用,可以将模块按需加载,动态地引入,注意是一个异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component } from 'react';

class App extends Component {
handleClick = () => {
import('./moduleA')
.then(({ moduleA }) => {
// Use moduleA
})
.catch(err => {
// Handle failure
});
};

render() {
return (
<div>
<button onClick={this.handleClick}>Load</button>
</div>
);
}
}

export default App;

router

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

import React from 'react'
import { render } from 'react-dom'

// 首先我们需要导入一些组件...
import { Router, Route, Link } from 'react-router'

// 然后我们从应用中删除一堆代码和
// 增加一些 <Link> 元素...
const App = React.createClass({
render() {
return (
<div>
<h1>App</h1>
{/* 把 <a> 变成 <Link> */}
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>

{/*
接着用 `this.props.children` 替换 `<Child>`
router 会帮我们找到这个 children
*/}
{this.props.children}
</div>
)
}
})

// 最后,我们用一些 <Route> 来渲染 <Router>。
// 这些就是路由提供的我们想要的东西。
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox} />
</Route>
</Router>
), document.body)
  • 在组件当中使用Link进行导航
  • 最后使用<Route>进行渲染,并提供相关的组件,支持路由的嵌套

参数设置

<Route path="messages/:id" component={Message} />当路由进行匹配的时候可以使用this.props.param.id来引用

路由首页设置

当我们在匹配路由是/的时候,希望渲染一个组件,与其他组件不是嵌套的父子关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { IndexRoute } from 'react-router'

const Dashboard = React.createClass({
render() {
return <div>Welcome to the app!</div>
}
})

React.render((
<Router>
<Route path="/" component={App}>
{/* 当 url 为/时渲染 Dashboard */}
<IndexRoute component={Dashboard} />
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
), document.body)

Dashboard将会被渲染在App组件的{this.props.children}当中

动态设置路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React from "react";
import { Route, Switch } from "react-router-dom";
import asyncComponent from "./components/AsyncComponent";
import AppliedRoute from "./components/AppliedRoute";
import AuthenticatedRoute from "./components/AuthenticatedRoute";
import UnauthenticatedRoute from "./components/UnauthenticatedRoute";

const AsyncHome = asyncComponent(() => import("./containers/Home"));
const AsyncLogin = asyncComponent(() => import("./containers/Login"));
const AsyncNotes = asyncComponent(() => import("./containers/Notes"));
const AsyncSignup = asyncComponent(() => import("./containers/Signup"));
const AsyncNewNote = asyncComponent(() => import("./containers/NewNote"));
const AsyncNotFound = asyncComponent(() => import("./containers/NotFound"));

export default ({ childProps }) =>
<Switch>
<AppliedRoute
path="/"
exact
component={AsyncHome}
props={childProps}
/>
<UnauthenticatedRoute
path="/login"
exact
component={AsyncLogin}
props={childProps}
/>
<UnauthenticatedRoute
path="/signup"
exact
component={AsyncSignup}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/new"
exact
component={AsyncNewNote}
props={childProps}
/>
<AuthenticatedRoute
path="/notes/:id"
exact
component={AsyncNotes}
props={childProps}
/>
{/* Finally, catch all unmatched routes */}
<Route component={AsyncNotFound} />
</Switch>
;

代码当中使用了asyncComponent这个函数来动态加载组件,注意操作是异步的