react快速入门

logo.jpg

虚拟DOM

本质:用js对象模拟DOM和嵌套关系
目的:实现DOM的高效更新(数据更新时,浏览器DOM会整个重新渲染,而虚拟DOM只渲染更新的部分、按需渲染)

1
2
3
4
5
6
7
8
9
10
//虚拟DOM的本质是个对象
<div id="xxx"><span>Hello</span></div>
['div',
{id: 'xxx'},
children:['span',
{},
'Hello'
]
]

将新的DOM树和旧的DOM树进行对比,得到改变的部分对其按需渲染
然而,浏览器没有提供api可以用来获取整棵DOM树,因此,需要程序员创建虚拟DOM
新旧DOM数对比.PNG

Diff算法【逐层比对】

  • tree diff:新旧两颗DOM树逐层对比(对比是否有增加或删除组件),如果有区别则重新渲染;如果没有区别,则进入下一步-> 组件对比
  • component diff:每一层中组件的对比;如果对比前后,组件的类型不同,则移除旧组件,创建新组件,然后追加到页面上;如果对比前后,组件的类型相同,再进入下一步 -> element diff
  • element diff:组件中,元素的对比(比如旧组件中的p标签变成了div标签)

开始一个React项目

1
2
3
4
5
npx create-react-app todoList
cd todoList
yarn start
yarn build #不需要写成yarn run build

配置package.json

在package.json中加上这些内容:

1
"homepage": "./",

在入口文件引入react

1
2
import React from 'react'; //创建React对象
import ReactDOM from 'react-dom'; //将React创建的对象渲染到页面上

React.createElement(参数1,参数2,参数3)

React.createElement接收3个参数:

  • 参数1:标签名(String)
  • 参数2:拥有的属性或者null(Object)
  • 参数3:子节点
1
<div id="title" class="xxx">hello world</div>
1
2
3
const myTitle = React.createElement('div', {id:'title', className:'xxx'}, 'hello world')
ReactDOM.render(myTitle, document.querySelector('#app'))

这种方法太过麻烦,React提供了JSX语法

JSX语法

1
2
3
4
5
6
7
const xxx = 'World';
const el=<div id="title" className="xxx">Hello {xxx}</div>
ReactDOM.render(
el,
document.querySelector('#app')
)

【点击查看代码】

1
2
3
在JSX中,`变量`用`{ }`括起来,花括号内的东西看作js语法
你可以用 花括号 表示任意的 JavaScript 表达式,比如{user.name}
如果要传字符串的话,`id="title"`或者`id={'title'}`

渲染成innerHTML

jsx默认渲染文本,如果你想渲染HTML标签,你必须使用dangerouslySetInnerHTML,并通过 __html 键传递一个对象

1
<h1 dangerouslySetInnerHTML={{__html: this.state.msg}}></h1>

【点击查看代码】

【点击查看代码】

更新已渲染的元素

React 元素是 不可突变(immutable) 的. 一旦你创建了一个元素, 就不能再修改其子元素或任何属性。一个元素就像电影里的一帧: 它表示在某一特定时间点的 冻结UI 。

就我们所知, 更新 UI 的唯一方法是创建一个新的元素, 并将其传入 ReactDOM.render() 方法

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

添加注释

jsx的注释,不能直接注释,要放在花括号内

1
2
3
4
{/* 推荐这种注释 */}
{// 这种方法必须换行,不然末尾的花括号也会被注释掉
}

className和htmlFor

在jsx中,class要写成className;label标签的for属性要写成htmlFor

列表循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const todos = [{name:'a',id:1},{name:'b',id:2},{name:'c',id:3},{name:'d',id:4}];
const el=<div id="title" className="red">
{todos.map((todo,index) =>
<h2 key={todo.id}>
{todo.name}</h2>
)
}
</div>
ReactDOM.render(
el,
document.querySelector('#app')
)

【点击查看代码】

如果列表没有key,也可以用index代替(原则上不推荐用index作为key)

1
<h2 key={index}> {todo.id} </h2>

条件渲染

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
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: false
}
this.login = this.login.bind(this)
}
login(){
this.setState({
isLogin:true
})
}
render() {
let msg = null;
if (this.state.isLogin) {
msg = <h1>登陆了</h1>
} else {
msg = <React.Fragment>
<h1>未登录</h1>
<button onClick={this.login}>登陆</button>
</React.Fragment>
}
return <React.Fragment>
{msg}
</React.Fragment>
}
}

【点击查看代码】

组件

组件:参考文档
组件名必须大写开头!<div />表示div标签,<Div />表示组件
组件中的props是只读的,无法进行修改

function组件/无状态组件

function组件通过props.xxx接收数据

你可以用ES5的构造函数来写一个组件,直接return一个jsx:

1
2
3
4
5
6
7
8
9
function Welcome(props) { //形参props接收数据
return <h1>Hello, {props.name}</h1>;
}
const el = <Welcome name="Sara" />;
ReactDOM.render(
el,
document.getElementById('root')
);

class组件/有状态组件

class组件通过this.props.xxx接收数据,通过this.state.xxx获取自身state里的数据

你也可以用ES6的class来写一个组件,通过render函数return一个jsx:

1
2
3
4
5
6
7
8
9
10
11
12
class Welcome extends React.Component {
render() {
const {name} = this.props;
return <h1>Hello, {name}</h1>; //组件接收数据
}
}
const el = <Welcome name="Sara" />; //给组件传数据
ReactDOM.render(
el,
document.getElementById('root')
);

【点击查看代码】

1
类组件不需要形参props接收数据,直接使用`{this.props.xxx}`接收

使用展开运算符

1
2
3
4
5
6
let obj = {
name:'stage',
age:24,
city:'shanghai'
}
const el = <Welcome name={obj.name} age={obj.age} city={obj.city} />; //给组件传数据。这种形式太繁琐!我们用下面这种展开运算符简写
1
2
3
4
5
6
7
8
9
10
11
12
13
let obj = {a:'hello',b:'world'}
function Hello(props){
return <h1>
{props.a}
{props.b}
</h1>;
}
ReactDOM.render(
<Hello {...obj} />,
document.querySelector('#app')
)

【点击查看代码】
展开运算符可以将 数组 或者 对象 的项进行展开

1
2
3
4
5
6
7
8
9
10
11
let obj1 = {a:1,b:2,c:3}
let obj2 = {
a:obj1.a,
b:obj1.b,
c:obj1.c
}
等同于
let obj2 = { ...obj1 }
注意!是值拷贝,不是地址拷贝,因此修改obj1的值,对obj2没有影响

function组件和class组件的区别

  • class组件有props,状态state,生命周期函数,因此称为“有状态组件”
  • function组件只有props,没有状态state和生命周期函数,因此称为“无状态组件”,运行效率比有状态组件高
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
xxx:'我是state中的xxx'
};
}
render() {
const {xxx} = this.state;
const {name} = this.props;
return <h1>
{xxx} - {name}
</h1>;
}
}
ReactDOM.render(
<Hello name="我是props中的name" />,
document.querySelector('#app')
)

合成组件

组件中,引入其他组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Hello(props){
return <h1>
hello,{props.name}
</h1>;
}
function People(props){
return <div>
<Hello name="Alice" />
<Hello name="Lisa" />
<Hello name="Benny" />
</div>
}
ReactDOM.render(
<People />,
document.querySelector('#app')
)

【点击查看代码】

提取组件

在src/components下新建一个Hello.jsx的组件

1
2
3
4
5
6
import React from 'react';
export default function Hello(props){
return <h1>
hello,{props.name}
</h1>;
}

或者

1
2
3
4
5
6
7
8
import React from 'react';
function Hello(props){
return <h1>
hello,{props.name}
</h1>;
}
export default Hello; //把组件导出去

在其他地方引入这个Hello组件:

1
import Hello from 'cp/Hello.jsx'

状态state

1
2
3
只有class组件才有state和生命周期
state里的数据是可读可写的【不能直接`this.statexxx` = '修改',而是`this.setState({xxx: '修改'});`】
props里的数据只读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
xxx:'我是state中的xxx'
};
}
render() {
const {xxx} = this.state;
const {name} = this.props;
return <h1>
{xxx} - {name}
</h1>;
}
}
ReactDOM.render(
<Hello name="我是props中的name" />,
document.querySelector('#app')
)

【点击查看代码】

props和state的区别

  • state里的数据是可读可写的,props里的数据只读
  • state里的数据是私有的(比如ajax请求),props里的数据是外部传入过来的

父子组件

父传子

1
2
3
4
5
父组件通过属性的形式传递参数`<Son xxx={this.state.a} />`
子组件通过props接收传递过来的参数
function组件写成:<div>{props.a}</div>
class组件写成:<div>{this.props.a}</div>

子组件:

1
2
3
4
5
6
7
8
import React from 'react';
function Son(props){//此处子组件不需要私有状态state,因此写成函数式组件
return <li>
{props.user}:{props.content}
</li>
}
export default Son;

父组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';
import Son from 'cp/Son.jsx'; //此处父组件中用到了子组件,因此要引入
class Father extends React.Component {
constructor(props) {
super(props);
this.state = {
lists:[
{id:1,user:'Dan',content:'哈哈哈'},
{id:2,user:'Alice',content:'你好'},
{id:3,user:'Ben',content:'沙发'},
{id:4,user:'Cat',content:'大海蓝天'}
]
};
}
render() {
return <ul>
{this.state.lists.map(list => <Son key={list.id} {...list} />)}
/* map在哪,则key在哪;因此key写在这,而不是子组件<li key={props.id}>上 */
</ul>;
}
}
export default Father;

然后在index.js中使用

1
2
3
4
5
6
7
8
9
10
import React from 'react';
import ReactDOM from 'react-dom';
import Father from 'cp/Father.jsx';
ReactDOM.render(
<Father />,
document.querySelector('#app')
)

【点击查看代码】

子传父 - 状态提升

React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。

状态提升

1
2
3
4
5
6
父组件将方法fn通过属性传递给子组件:<Son fn={this.fn.bind(this)} />
子组件通过调用传过来的方法,将数据作为参数传给父组件:(
handleFn(){
this.props.fn(子组件的数据) //调用fn,并把数据放在参数里
}

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
class Son extends React.Component {
constructor(props){
super(props);
this.handleChangeData = this.handleChangeData.bind(this);
}
handleChangeData(){
this.props.changeData('传递子组件的数据') // //将子组件的数据传给父组件
}
render(){
return <li onClick={this.handleChangeData}>{this.props.a}</li>
}
}
class Father extends React.Component {
constructor(props) {
super(props);
this.state = {
a:'父组件的数据'
};
this.changeData = this.changeData.bind(this);
}
changeData(data){
this.setState({
a:data
})
}
render() {
return <div>
<h1>子组件向父组件传数据</h1>
<Son a={this.state.a}
changeData={this.changeData}
/>
</div>
}
}

【点击查看代码】

Context

context 用于共享全局数据,类似于Vue中的eventBus。
context 一般用于传递 用户是否登录,主题色或首选语言等。
不要仅仅为了避免几个层级的组件传递数据而使用context!!

下面是一个爷孙组件的例子,如果数据通过 爷爷 -> 爸爸 -> 儿子,这样太繁琐。我们使用context,将爷爷的数据作为 全局数据 ,从而让孙子获得

新建一个xxx-context.js文件

1
2
3
import React from 'react'
const XxxContext = React.createContext(); //相当于const eventBus = new Vue();
export default XxxContext;

然后在需要用到该context的组件中引入:

1
import XxxContext from './xxx-context.js'

通过将根组件包裹在XxxContext..Provider标签内,然后通过value共享全局数据。
这样,包裹在该标签下的所有组件,都能访问到value共享的数据

1
2
3
4
5
render(){
return <XxxContext.Provider value={this.state.msg}>
<Father />
</XxxContext.Provider>
}

子孙组件通过<XxxContext.Consumer>来获取数据,注意,该标签返回一个回调函数!!!需要通过value => {}来展示数据

1
2
3
4
5
6
7
8
9
10
11
12
class Son extends React.Component{
render(){
return <XxxContext.Consumer>
{
value => {
console.log(value);
return <h1>{value}</h1>
}
}
</XxxContext.Consumer>
}
}

【点击查看代码】

在jsx中写style样式

1
2
style样式写成对象`{}`的形式,然后放在大括号内
`{{color:'red',fontSize:'20px'}}`或者`{{color:'red', 'font-size':'20px'}}`
1
2
3
4
5
6
7
8
9
10
function App(props){
return <h1 style={{color:'red','font-style':'italic'}}>Hello
<h2 style={{color:'blue'}}>world</h2>
</h1>
}
ReactDOM.render(
<App />,
document.querySelector('#app')
)

把样式对象抽离出来:

1
2
3
4
5
6
7
8
const h1Style = {color:'red','font-style':'italic'};
const h2Style = {color:'blue'};
function App(props){
return <h1 style={h1Style}>Hello
<h2 style={h2Style}>world</h2>
</h1>
}

继续精简,把所有的样式放在一个样式表对象中:

1
2
3
4
5
6
7
8
9
10
const styles = {
h1: {color:'red','font-style':'italic'},
h2: {color:'blue'}
};
function App(props){
return <h1 style={styles.h1}>Hello
<h2 style={styles.h2}>world</h2>
</h1>
}

最后,将styles抽离成单独的【样式模块】
新建styles.js文件

1
2
3
4
export defaulf {
h1: {color:'red','font-style':'italic'},
h2: {color:'blue'}
};

别处引入该样式模块:

1
import styles from 'modules/styles.js'

【点击查看代码】

css模块化

在vue中,使用scoped来作为组件私有样式,不加scoped的样式则为全局样式
在react,如果直接导入import 'css/styles.css'则为全局样式;如果想要组件私有样式,则:import cssObj from 'css/styles.css'
注意!!!需要在webpack中配置css-loader:css-loader?modules&localIdentName=[path][name]-[local]-[hash:8],否则无法使用css模块

父组件的className为xxx,如果直接import 'css/styles.css'则为全局样式,会导致子组件<Son />中的字体颜色也为红色,因此需要css模块化
这里的类选择器和id选择器会替换成[path][name]-[local]-[hash:8],如果想让样式表中的某个样式时全局样式的话,可以使用:global(类选择器orid选择器)

1
2
3
4
5
6
7
8
/* styles.css */
.xxx{
color:red;
}
:global(.yyy){
font-style: italic;
}

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
import React from 'react';
import Son from './Son.jsx';
import cssObj from './styles.css'; //css模块化
export default class Father extends React.Component {
constructor(props) {
super(props);
this.state = {
lists: [
{ id: 1, user: "Dan", content: "哈哈哈" },
{ id: 2, user: "Alice", content: "你好" },
{ id: 3, user: "Ben", content: "沙发" },
{ id: 4, user: "Cat", content: "大海蓝天" }
]
};
}
render() {
return (
<ul className={cssObj.xxx}> /* 使用css模块 */
<h1>父组件的颜色</h1>
{this.state.lists.map(list => <Son key={list.id} {...list} />)}
</ul>
);
}
}

注意!!css模块化只对类选择器和id选择器生效,对于标签选择器无效。

如果有多个样式,可以写成数组的形式,然后用join将逗号变成空格:

1
<ul className={[cssObj.xxx, 'yyy'].join(' ')}></ul>

绑定事件

html中,绑定事件:

1
<button onclick="xxx()">点我</button>

在jsx中绑定事件:

1
2
3
4
5
6
7
8
function xxx(){
console.log(1)
}
ReactDOM.render(
<button onClick={xxx}>点我</button>,
document.querySelector('#app')
)

在组件中绑定事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {a:'hello'};
this.xxx = this.xxx.bind(this);
};
render() {
return <button onClick={this.xxx}>点我</button>;
};
xxx(){
console.log(this.state.a)
}
}
ReactDOM.render(
<Button />,
document.querySelector('#app')
)

this的指向问题

在JSX回调中你必须注意 this 的指向。 在 JavaScript 中,类方法默认没有 绑定 的。如果你忘记绑定 this.xxx 并将其传递给onClick,那么在直接调用该函数时,this 会是 undefined 。

你可以通bind(this)来绑定this:

1
<button onClick={this.xxx.bind(this,参数1,参数2)}>点我</button>

这种写法,不用传入ev;回调函数xxx的最后一个参数默认是ev

1
2
3
xxx(参数1,参数2,ev){
//最后一个参数默认是ev
}


或者可以通过箭头函数解决this的指向问题:

1
<button onClick={ (ev)=> this.xxx(参数1,参数2,ev) }>点我</button>

这种写法,ev需要显式地传入回调函数xxx中,箭头函数中ev放在第几个,那么在回调函数中,ev就是第几个参数【即,ev的位置手动决定】

【点击查看代码】

setState()的注意点

setState是异步的,如果有多次setState,会合并成一次setState,从而提升性能

由于setState是异步的,因此:

1
2
3
4
5
6
xxx(ev){
this.setState({a:'我被修改了'});
console.log(this.state.a)
}
//会先打印出初始值,然后执行修改

1
如果想要修改后,立即获取修改后的值,想要使用:`this.setState({新数据},回调函数)`:
1
2
3
4
5
xxx(ev){
this.setState({a:'我被修改了'}, () => {
console.log(this.state.a) //当数据更新完成后,才调用回调函数
});
}

【点击查看代码】

最新React建议使用以下setState

1
2
3
4
5
6
7
8
9
10
11
12
//老版
this.setState({msg:'Hello'})
//新版不再推荐传入对象,而是一个函数,该函数return一个对象
const msg = 'Hello'; //建议将值先赋给一个变量进行保存
this.setState(()=> {
return {msg}
})
//ES6中,箭头函数return的简写形式↓
const msg = 'Hello'; //建议将值先赋给一个变量进行保存
this.setState(() => ({msg}))

input的数据双向绑定

必须给input提供value和onChange

受控组件:value由state里的值决定

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
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {a:'hello'};
this.xxx = this.xxx.bind(this);
};
xxx(ev){
this.setState({
a:ev.target.value /* 或者通过ref获取target */
})
}
render() {
const {a} = this.state;
return <div>
<input value={a}
onChange={this.xxx}
/>
<p>{a}</p>
</div>;
};
}
//最新React推荐这种写法
xxx(ev){
const a = ev.target.value;
this.setState((state) => ({a}))
}

【点击查看代码】

ref获取DOM元素

在Vue中,通过this.$refs获取DOM元素
而在React中,通过this.refs获取DOM元素

非受控组件:value值由DOM元素决定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {a:'hello'};
this.xxx = this.xxx.bind(this);
this.input = React.createRef();
};
xxx(ev){
console.log(this.input)
this.setState({
a:this.input.current.value //通过this.xxx.current访问ref
})
}
render() {
return <div>
<input ref={this.input}
value={this.state.a}
onChange={this.xxx}
/>
<p>{this.state.a}</p>
</div>;
};
}

【点击查看代码】

ref的另一种写法

在新版React中,推荐通过ref={xxx=>this.xxx=xxx}的形式引用DOM

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class App extends React.Component {
handleClick(){
console.log(this.xxx.innerHTML)
}
render(){
return (
<h1 onClick={this.handleClick.bind(this)}
ref={xxx=>this.xxx=xxx}>Hello</h1>
)
}
}
ReactDOM.render(
<App />,
document.querySelector('#app')
)

JS Bin on jsbin.com

生命周期函数

生命周期函数指的是,在某一时刻组件会自动去调用执行的函数
一个React组件的生命周期分为三个部分:挂载期(Mounting)、存在更新期(Updating,外界传入props或自身state改变时触发)和销毁时(Unmounting)

react生命周期.PNG

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
51
52
53
54
55
56
import React,{ Component } from 'react';
class Demo extends Component {
constructor(props) {
super(props);
this.state = {
//定义state
dataList: []
}
}
componentWillMount () { //类似Vue的beforeMoute,很少使用
}
componentDidMount () { //类似Vue的mounted,挂载完成,可获取到 DOM 节点。ajax请求放在这个生命周期中,因为该生命周期函数只执行一次
axios.get('/api')
.then(res => {
this.setState({
dataList: res.data
})
})
}
//当同时满足以下2点时,会执行该函数
//1.父组件传过来props
//2.父组件的render函数重新执行时,则子组件的componentWillReceiveProps也会被执行
componentWillReceiveProps (nextProps) {
}
//用于提升性能,父组件更新,而传入的props不变时,防止子组件跟着父组件更新
shouldComponentUpdate (nextProps,nextState) { //需要return一个布尔值;
if(nextProps.msg !== this.props.msg){ //如果传过来的props.msg更新了,就返回true
return true;
}else { //如果props.msg没变化,则返回false
return false;
}
}
}
componentWillUpdate (nextProps,nextState) { //类似Vue的beforeUpdate
}
componentDidUpdate (prevProps,prevState) { //类似Vue的updated
}
render () {
return (
<div>{this.props.msg}</div>
)
}
componentWillUnmount () { //类似Vue的beforeDestroy
}
}
export default Demo;

数组的操作

数组的添加:
在vue中,给数组添加一项用this.lists.push('xxx')即可
而在react中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
addItem(){
const lists = [...this.state.lists];
lists.push('xxx');
this.setState({
lists
})
}
或者
addItem(){
this.setState({
lists: [...this.state.lists, newVal]
})
}

数组的删除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
deleteItem(i){
const lists = [...this.state.lists];//值拷贝,而不是地址拷贝
lists.splice(i,1);//删除第i项
this.setState({
lists
})
}
//新版推荐setState里return一个对象
deleteItem(i){
this.setState((state) => {
const lists = [...state.lists];
lists.splice(i,1);
return {lists}
})
}

虽然下面这种做法也行,但不推荐使用↓

1
2
3
4
5
deleteItem(i){
this.setState({
lists: this.state.lists.splice(i,1)
})
}

在react中,不建议直接修改state中的数据;最好先进行拷贝,然后修改拷贝的数据,最后再把修改完成的拷贝数据赋值给原始数据!

一个todoList实例

React.Fragment作为根节点

React要求只能有一个根节点,如果用<div></div>包裹,最终生成的html会有多余的div标签;通过<React.Fragment>或者<></>,生成的最终html不包含该标签

1
2
3
4
5
6
7
8
9
10
11
12
<React.Fragment>
<div>1</div>
<div>2</div>
<div>3</div>
</React.Fragment>
等同于
<>
<div>1</div>
<div>2</div>
<div>3</div>
</>

使用 PropTypes 进行类型检查

http://react.css88.com/docs/typechecking-with-proptypes.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import PropTypes from 'prop-types';
class Hello extends React.Component {
render() {
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.male ? '男' : '女'}</li>
</ul>
);
}
}
Hello.propTypes = {
name: PropTypes.string,//父组件传过来的name必须是string类型
age: PropTypes.number,
male: PropTypes.bool.isRequired, //父组件必须传进来male,否则warning
};
export default Hello;

defaultProps设定默认值

http://react.css88.com/docs/typechecking-with-proptypes.html#%E9%BB%98%E8%AE%A4%E7%9A%84-prop-%E5%80%BC

1
2
3
Hello.defaultProps = {
name: 'stage' //如果父组件没有传入name,则默认为'stage'
};

React过渡动画

通过transition

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
class App extends React.Component {
constructor(props){
super(props);
this.state = {
isVisible: true
}
this.toggle = this.toggle.bind(this)
}
toggle(){
this.setState({
isVisible: !this.state.isVisible
})
}
render(){
return (
<>
<h1 className={this.state.isVisible ? 'show' : 'hide'}>Hello</h1>
<button onClick={this.toggle}>切换</button>
</>
)
}
}
export default App;

【点击查看代码】

通过@keyframes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.show{
animation:show-hello 1s forwards;
}
.hide{
animation:hide-hello 1s forwards;
}
@keyframes hide-hello {
0%{opacity:1;color:red;}
50%{opacity:.5;color:green;}
100%{opacity:0;color:blue;}
}
@keyframes show-hello {
0%{opacity:0;color:blue;}
50%{opacity:.5;color:green;}
100%{opacity:1;color:red;}
}

【点击查看代码】

通过react-transition-group模块实现动画

https://reactcommunity.org/react-transition-group/

通过yarn add react-transition-group安装:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { CSSTransition } from 'react-transition-group';
render(){
return (
<>
<CSSTransition
in={this.state.isVisible}
timeout={300}
classNames='xxx'
unmountOnExit //exit时,移除DOM节点
onEntered={el=>el.style.color='red'} //进入完成后执行该钩子函数
appear={true} //一开始显示的动画,需添加.xxx-appear.xxx-appear-active
>
<h1>Hello</h1>
</CSSTransition>
<button onClick={this.toggle}>切换</button>
</>
)
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.xxx-enter, .xxx-appear{
opacity: 0;
}
.xxx-enter-active, .xxx-appear-active{
opacity: 1;
transition: 1s;
}
.xxx-enter-done{
opacity: 1;
}
.xxx-exit{
opacity: 1;
}
.xxx-exit-active{
opacity: 0;
transition: 1s;
}
.xxx-exit-done{
opacity: 0;
}

【点击查看代码】

给多个元素添加动画样式

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
import { CSSTransition,TransitionGroup } from 'react-transition-group';
render(){
return (
<>
<TransitionGroup>
{
this.state.lists.map((list,index)=>{
return (
//多个元素添加动画时,不需要设置in
<CSSTransition
key={index} //key值放在循环的最外层上
classNames='xxx'
timeout={300}
onEntered={el=>el.style.color='red'}
>
<h1>{list}</h1>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={this.toggle}>添加</button>
</>
)
}

【点击查看代码】

UI组件 vs 容器组件

  • UI组件负责渲染,即function组件,自身没有state
  • 容器组件负责逻辑

在容器组件中,通过属性的方式将数据传给UI组件

1
2
3
4
5
6
7
8
9
10
11
12
13
//TodoList.jsx
import TodoListUI from './TodoListUI.jsx'
render(){
return (
<TodoListUI
inputValue={this.state.inputValue}
handleInputChange={this.handleInputChange}
btnClick={this.btnClick}
deleteItem={this.deleteItem}
/>
)
}

在UI组件中,通过props.xxx接收数据

1
2
3
4
5
6
7
8
9
10
11
12
//TodoListUI.jsx
import React from 'react';
function TodoListUI(props){
return (
<>
<input value={props.value} onChange={props.handleInputChange} />
<button onClick={props.btnClick}>点击</button>
{/* 如果要传递参数,则写成箭头函数的形式↓ */}
<button onClick={() => props.deleteItem(index)}>删除</button>
</>
)
}

查看在线demo

补充

  • props由外界传入
  • state是自身的

优化性能

文档:优化性能

  1. 将this的绑定放在constructor里this.xxx = this.xxx.bind(this)
  2. setState是异步的,会自动把多次setState合并成1次,降低虚拟DOM的比对频率
  3. 通过设置key值,提高比对速度
  4. shouldComponentUpdate阻止不必要的render

新的setState

1
2
3
4
5
6
7
8
//新版不再推荐传入对象,而是一个函数,该函数return一个对象
axios.get('/api')
.then(res=>{
this.setState(state=>({
lists:[...state.list, res.data]
}))
})
-------------本文结束感谢您的阅读-------------