标签类型

JSX语法中,标签类型有2种,DOM类型的标签和React组件类型的标签。DOM类型的标签首字母必须小写,React组件类型的标签首字母必须大写,React正式通过首字母的大小写判断渲染的是一个DOM类型的标签还是React类型的标签

1
2
3
4
5
6
7
8
9
10
// Dom
const element = <h1> hello </h1>
// react组件类型的标签
const element = <HelloWorld/>
// 俩者可互相嵌套
const element = (
<div>
<HelloWorld />
</div>
)

组件跟元素

React元素就是一个普通的js对象,这个对象通过DOM节点或React组件描述界面是什么样的,jsx语法就是用来创建React元素的

1
2
// react元素
const element = <h1>Hello, world</h1>;

React组件是个class或函数,接受一些属性作为输入,返回一个React元素,React组件是由若干React元素组建而成

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 Button extends React.Component {
render() {
return (<button>OK</button>)
}
}
// 在jsx中使用组件Button,button是一个代表组件Button的React元素
const button = <Button />
// 在组件中使用react元素button
class Page extends React.Component {
render() {
return (
<div>
{button}
</div>
)
}
}
// 上面的写法等价于下面
class Page extends React.Component {
render() {
return (
<div>
<Button />
</div>
)
}
}

状态提升 子组件如何传值给父组件

其实还是父组件改变自己的状态,只不过是父组件改变状态的方法传给子组件,子组件调用父组件的方法 ,改变父组件的状态,有点像Vue里的$emit('handleChange', value)

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
//child.js
export default class TemperatureInput extends React.Component {
// 调用父组件传过来的handleChange方法
handleChange(e) {
this.props.handleChange(e.target.value)
}
render() {
const temp = this.props.temp
return (
<fieldset>
<input
value={temp}
onChange={e => this.handleChange(e)}
/>
</fieldset>
)
}
}
// parent.js 通过props方式向子组件传入handleChange属性
export class Calcuator extends React.Component {
constructor(props) {
super(props)
this.state = {
temp: '',
scale: 'c'
}
}
handleCelsiusChange(temp) {
this.setState({scale: 'c', temp});
}
handleFahrenheitChange(temp) {
this.setState({scale: 'f', temp});
}
render() {
const temp = this.state.temp
const scale = this.state.scale
const celsius = scale === 'f' ? Util.tryConvert(temp, Util.toCelsius) : temp;
const fahrenheit = scale === 'c' ? Util.tryConvert(temp, Util.toFahrenheit) : temp;
return (
<div>
<TemperatureInput scale='c' temp={celsius} handleChange={(e) => this.handleCelsiusChange(e)}/>
<TemperatureInput scale='f' temp={fahrenheit} handleChange={(e) => this.handleFahrenheitChange(e)}/>
</div>
)
}
}

组合和继承

跟Vue里的slot有点像,占坑!!
组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

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
export function SplitPane(props) {
return (
<div className='SplitPane'>
<div className='SplitPane-left'>
{props.left}
</div>
<div className='SplitPane-right'>
{props.right}
</div>
</div>
)
}
export function ParentPanel() {
return (
<SplitPane
left={
<Contacts name='contact'/>
}
right={
<Chat name='chart'/>
}
/>
)
}
function Contacts(props) {
return <div className="Contacts" >左边{props.name}</div>;
}
function Chat(props) {
return <div className="Chat" >中间位置{props.name}</div>;
}

yarn build之后查看打包之后的文件

执行yarn build之后,create react app会给一下提示

1
2
3
4
yarn global add serve
serve -s build
// 然后在package.json script添加
"server": "serve -s build"

想要浏览打包后的静态文件,即执行yarn server即可

code split

基于路由的代码分割,有一个前提得是
React.lazy接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);

全局状态 Context

react-devtools安装和调试

国内下载github里的资源太难了

码云还是不错的react-devtools,这篇文章写得还行react-devtools安装以及使用中的问题

HOC高阶组件

实际上装饰器模式的应用!
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式,
具体而言,高阶组件是参数为组件,返回值为新组件的函数。

HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用。

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
import React from 'react'
export class MyComponent extends React.Component {
render() {
return (
<>
<div>{this.props.data}</div>
</>
)
}
}
function WithPerData(WrappedComponent, key) {
class WithSubscription extends React.Component {
constructor(props) {
super(props)
this.state = {
data: ''
}
}
componentDidMount() {
let data = localStorage.getItem(key)
this.setState({data})
}
render() {
return (
<div style={{backgroundColor: 'red'}}>
<WrappedComponent data={this.state.data} {...this.props}/>
</div>
)
}
}
WithSubscription.displayName = 'WithSubscription(Component)'
return WithSubscription
}
export const MyComponentWithPerData = WithPerData(MyComponent, 'data1')
export const MyComponentWithPerData2 = WithPerData(MyComponent, 'data2')

约定:最大化可组合性

1
2
// React Redux 的 `connect` 函数
const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

把它分开看

1
2
3
4
// connect 是一个函数,它的返回值为另外一个函数。
const enhance = connect(commentListSelector, commentListActions);
// 返回值为 HOC,它会返回已经连接 Redux store 的组件
const ConnectedComment = enhance(CommentList);

换句话说,connect 是一个返回高阶组件的高阶函数!这种形式可能看起来令人困惑或不必要,但它有一个有用的属性。 像 connect 函数返回的单参数 HOC 具有签名 Component => Component。 输出类型与输入类型相同的函数很容易组合在一起。

1
2
3
4
5
6
7
8
9
10
// 而不是这样...
const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))
// ... 你可以编写组合工具函数
// compose(f, g, h) 等同于 (...args) => f(g(h(...args)))
const enhance = compose(
// 这些都是单参数的 HOC
withRouter,
connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)