1. 常用组件

组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。组件,从概念上类似于 Javascript 函数。它接受任意的入参(props),并返回用于描述页面展示内容的 React 元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// DOM标签
const element = <div>hello</div>;

// 自定义组件
const element1 = <Welcome name="Sara"></Welocme>

// class组件
class Welcome extends React.Component {
render() {
return <h1>hello, {this.props.name}</h1>
}
}

// 函数组件: 定义组件最简单的方式就是编写javascript函数
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
</div>
)
}

1.1 条件渲染

  • 与运算符 &&
1
2
3
4
5
6
7
8
9
10
function Mailbox(props) {
const unreadMessage = props.unreadMessage;
return (
<div>
{unreadMessage.length > 0 && (
<h2>you have {unreadMessage.length} unread message.</h2>
)}
</div>
);
}
  • 三目运算符
1
2
3
4
5
6
7
8
9
10
11
12
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutBtn />
) : (
<LoginBtn />
)}
</div>
)
}

1.2 列表渲染

Array.map实现列表渲染,key 帮助 React 识别哪些元素改变了,比如被添加或删除。数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的,当我们生成两个不同的数组时,我们可以使用相同的 key。

1
2
3
4
5
6
7
8
9
10
function NumberList(props) {
const items = props.items;
return (
<ul>
{items.map(item, index) =>
<li key={item.id}>{item.text}</li>
}
</ul>
)
}

1.3 表单组件

表单元素的工作方式和其他的 DOM 元素有些不同,表单元素通常会保持一些内部的 state。

1
2
3
4
5
6
7
8
9
// 表单元素状态由使用者维护 <input type="text" value={this.state.value}
onChange={(e) => this.setState({ value: e.target.value })} />

<select value="{this.state.value}" onChange="{this.handleChange}">
<option value="1">1</option>
<option value="1">1</option>
</select>

// 表单元素状态DOM自身维护 <input type="text" ref={node => this.input = node} />

1.4 props.children

有些组件无法提前知道它们子组件的具体内容。通过props.children可以将他们的子组件渲染到结果中。

1
2
3
4
5
6
7
8
9
10
11
12
function FancyBorder(props) {
return <div className={'FancyBorder-' + props.color}>{props.children}</div>;
}

function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1>welcome</h1>
<p>Thank for coming!</p>
</FancyBorder>
);
}

1.5 React.lazy 与 Suspense

React.lazy能让你像渲染常规组件一样处理动态引入。你可以将Suspense组件置于懒加载组件之上的任何位置,fallback属性接受任何在组件加载过程中你想展示的 React 元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
<AnotherComponent />
</Suspense>
</div>
);
}

配合 React Router,基于路由进行代码分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
<Route>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
</Switch>
</Suspense>
</Router>
)

1.6 Fragments

React常常是一个组件返回多个元素,Fragments允许你将子列表分组,而无需向DOM添加额外节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
</React.Fragment>
)
}

// 短语法:空标签
return (
<>
<td>hello</td>
<td>world</td>
</>
)

`

1.7 高阶组件

  • 在挂载时,想DataSource添加一个更改侦听器
  • 在侦听器内部,当数据源发生变化时,调用setState
  • 在卸载时,删除侦听器
    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
    const CommentListWithSubscription = withSubscription(
    CommonList,
    (DataSource) => DataSource.getComments()
    )

    function withSubscription(WrappedComponent, selectData) {
    return class extends React.Component {
    constructor(props) {
    super(props);
    this.state = {
    data: selectData(DataSource, props)
    }
    }

    componentDidMount() {
    DataSoure.addChangeListener(this.handleChange)
    }

    componentWillUnmout() {
    DataSource.removeChangeListener(this.handleChange)
    }

    handleChange = () =>{
    this.setState({
    data: selectData(DataSource, this.props)
    })
    }

    return() {
    return <WrappedComponent data={this.state.data} {...this.props}>
    }
    }
    }
    `

1.8 在一个模块中导出多个多组件

2. 结合redux

展示组件 容器组件
定义 不关心数据来源和如何变化,传入什么就渲染什么 监听redux store变化并过滤出要显示的数据
作用 描述如何展示(骨架、样式) 描述如何运行(数据获取、状态更新)
直接使用Redux
数据来源 props 监听Redux state
数据修改 从props调用回调函数 向Redux派发actions
调用方式 手动 通常由React Redux生成

2,1 无状态组件

使用函数式无状态组件,如果需要使用本地 state, 生命周期方法,性能优化,可以将它们转成 class。
技术上你可以直接使用store.subscribe()来编写容器组件,但不建议这样做,应使用 React Redux 的connect()方法生成容器组件。

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
const Todo = ({ onClick, completed, text }) => (
<li onClick={onClick} style={{ color: completed ? 'green' : 'red' }}>
{text}
</li>
);

const TodoList = ({ todos, onTodoClick }) => (
<ul>
{todos.map((todo, index) => (
<Todo key={index} {...todo} onClick={() => onTodoClick(index)} />
))}
</ul>
);

const Link = ({ active, children, onClick }) => {
if (active) {
return <span>{children}</span>;
}

return (
<a
href=""
onClick={e => {
e.preventDefault();
onClick();
}}
>
{children}
</a>
);
};

2.2 有状态组件

有状态组件,就是使用store.subscribe()从Redux state树中读取部分数据,并通过props来把这些数据提供给要渲染的组件。但建议使用React Redux库的connect()来生成,这个方法做了避免了很多不必要的重复渲染。

1
2
3
4
5
6
7
8
class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Header />
}
}

2.3 全局组件

通过Message.error('错误')来显示组件。

3. 组件传值

3.1 共同父组件

状态提升,将多个组件中需要共享的state向上移动到他们的最近共同父组件中,便可实现共享state

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Parent extends React.Component {
state = {
scale : ''
}

handleChange(val) => {
this.setState({scale: val})
}

render() {
const { scale } = this.state;
return (
<ChildOne scale={scale} onScaleChange={this.handleChange} />
<ChildTwo scale={scale} onScaleChange={this.handleChange} />
)
}
}

使用下面技巧,可以减少 props 的传递

1
2
3
4
5
6
7
8
9
function Page(props) {
const { user } = props;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={useLink} />;
}

3.2 Context 公用属性

React中数据是通过props属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(如:地区偏好,UI主题),这些属性是应用程序中许多组件都需要的。Context提供了一种在组件之间共享此值的方式,而不必显示地通过组件树的逐层传递props。

3.3 Refs

将DOM Refs暴露给父组件。你希望在父组件汇总引用子节点的DOM节点。
不能Ref高阶组件(connect到redux的HOC组件),ref出来的会是高阶组件,不是本组件。

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
class Child extends React.Component{
state = {
visible: false,
}

showModal() {
this.setState({
visible: true,
})
}

render() {
return (
<>
{ this.state.visible && <div>modal</div>}
</>
)
}
}

class Parent extends React.Component{
constructor(props){
super(props);
this.myRef = React.createRef()
}

handleClick = () => {
// 调用子组件方法
const node = this.myRef.current;
node.showModal();
}

render() {
return (
<>
<button onClick={handleClick}>触发子组件方法</button>
<Child ref={this.myRef} />
</>
)
}
}

4. 生命周期

4.1 挂载

当组件实例被创建并插入DOM中时,其生命周期调用顺序如下

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

4.2 更新

当组件的props或state发生变化时会触发更新。组件更新的生命周期调用顺序如下

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

4.3 卸载

当组件从DOM中移除之前,会调用

  • compoenntWillMount

参考资料

https://github.com/xitu/gold-miner/blob/master/TODO/our-best-practices-for-writing-react-components.md
https://juejin.im/post/5a73d6435188257a6a789d0d