React 组件基础

React 组件基础

一月 16, 2021

了解 React

  1. react 是什么?
    • react 是一个视图层框架,类似 MVC 中的 V
      • 视图
        • 将后端的数据高效的渲染到视图
        • 将用户输入的信息高效的展示在界面
    • react 具有组件系统
      • 组件是一个独立的聚合体,复用性高,比如: 车上的零件坏了
    • react 是单向数据流
      • 树型结构的形式
  2. react 特点
    1. 虚拟 DOM
    2. DIFF 算法【16 版本之后更新为 Filber 算法】
    3. 组件系统化
    4. 单向数据流
    5. jsx
    6. 函数式编程
    7. 插件化编程
    8. 一切皆组件

react 脚手架使用

  1. create-react-app
    1
    2
    $ cnpm i create-react-app -g    # 全局安装
    $ create-react-app # 创建项目
  2. dva
  3. umi

类组件/函数组件

  1. 在 src 目录下创建 index.js 入口文件和 App.js 组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// App.js
// 引入
import React from "react";

//! 类组件写法
class App extends React.Component {
render() {
return <div>这是React类组件写法</div>;
}
}
//! 函数组件写法 -------------
// const App = () => {
// return (
// <div>
// 这里是函数组件
// </div>
// )
// }

// 导出
export default App;
  1. 在 index.js 中引入并使用
1
2
3
4
5
6
7
// index.js
// 引入
import ReactDOM from "react-dom";
import App from "./App";

// ReactDOM.render(react元素, 容器)
ReactDOM.render(<App />, document.querySelector("#root"));

组件嵌套

入口文件 index.js/Farther.js/Son.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Farther.js
import React from "react";
import Son from "./Son";

class Farther extends Component {
render() {
return (
<div>
Far
<Son />
</div>
);
}
}

export default Farther;
1
2
3
4
5
6
7
8
9
10
// Son.js
import React from "react";

class Son extends React.Component {
render() {
return <div>Son</div>;
}
}

export default Son;
1
2
3
4
5
// index.js
import ReactDOM from "react-dom";
import Farther from "./Farther";

ReactDOM.render(<Farther />, document.querySelector("#root"));

组件组合

this.props.children 可以类似插槽的功能,用于组合,接下来每个 index.js 入口文件都不写了

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
// App.js
import React, { Component } from 'react'
class Todolist extends Component {
render() {
return (
<div>
<h1>Todolist</h1>
{ this.props.children }
</div>
)
}
}

class Tab extends React.Component{
render () {
return <div>
<h1> tab </h1>
</div>
}
}
class Content extends React.Component{
render () {
return <div>
<h1> content </h1>
</div>
}
}
class TabBar extends React.Component{
render () {
return <div>
<h1> TabBar </h1>
</div>
}
}

class App extends Component {
render() {
return (
<div>
<TodoList>
<Tab/>
<Content/>
<TabBar>
</TodoList>
</div>
)
}
}

export default App

react 组件的样式

全局样式

  • 写法 import ‘xxx.css/scss/less’
  • 渲染到界面时类名就是自定义类名

局部样式

  • xxx.module.css 局部样式
  • import styles from ‘xxx.module.css’

cra 使用 sass

  1. 需要安装两个插件 node-sass sass-loader
1
$ yarn add node-sass sass-loader -D

cra 使用 less

  1. 找到 sass 配置 config 并拷贝
  2. 安装 less 和 less-loader
1
$ yarn add less less-loader -D

行内样式

在标签中加 style 属性 并用双括号

1
2
3
4
5
6
7
8
9
10
11
12
<p
style={{
width: "100px",
height: "100px",
background: "red",
}}
></p>
/*
为什么react中不允许 style="width: 100px;height: 100px;background:red"
1. jsx也是虚拟DOM写法
2. 虚拟DOM是一个对象模型,要求是对象的写法
*/

类名的操作/classnames 插件

1
$ yarn add classnames -D
1
2
3
4
5
6
7
8
9
import cl from "classnames";

<p
className={cl({
a: true,
b: true,
c: false,
})}
></p>;

样式组件

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";

//! 样式组件
import styled from "styled-components";

//todo 创建样式组件
const Container = styled.div`
width: 100px;
height: 100px;
background: ${(props) => props.color || "red"};
`;

export default class App extends Component {
color = "pink";
render() {
return (
<div>
<Container />
<Container color={this.color} />
</div>
);
}
}

react 事件

合成事件

案例:点击按钮弹出框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { Component } from "react";

export default class App extends Component {
// 在class中直接写事件处理程序
fn = () => {
alert("111");
};

render() {
// react合成事件绑定在了虚拟DOM上,原理用了事件委托
return (
<div>
<button onClick={this.fn}>点击</button>
</div>
);
}
}

事件对象

案例:回车时弹出 input 的 value 值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React, { Component } from "react";

export default class App extends Component {
getVal = (e) => {
if (e.keyCode == 13) {
alert(e.target.value);
}
};

render() {
return (
<div>
<input type="text" placeholder="请输入..." onKeyDown={this.getVal} />
</div>
);
}
}

事件传参

事件传参时需要加一层箭头函数

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

export default class App extends Component {
fn = (n) => {
alert(n);
};

render() {
return (
<div>
<button
onClick={() => {
this.fn(10);
}}
>
点击
</button>
</div>
);
}
}

this 绑定问题

问题: 普通函数中的 this 丢失, 是普通函数不是箭头函数
原因: 引文事件源这个 button 是虚拟 DOM
解决: bind

  1. 调用时绑定 bind this.fn.bind(this)
  2. 在构造函数中绑定 this constructor () { super(); this.fn = this.fn.bind(this) }
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
import React, { Component } from "react";

export default class App extends Component {
// 解决方式一
// constructor() {
// super()
// this.fn = this.fn.bind(this)
// }

// fn() {
// console.log('普通函数this',this);
// }

// render() {
// return (
// <div>
// <button onClick={this.fn}>按钮</button>
// </div>
// )
// }

// 解决方式二 -----------
fn() {
console.log("普通函数this", this);
}

render() {
return (
<div>
<button onClick={this.fn.bind(this)}>按钮</button>
</div>
);
}
}

原生事件

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

export default class App extends Component {
componentDidMount() {
// 和vue中mounted功能一样
// 少用
const btn = document.querySelector("button");
btn.onclick = function () {
alert("原生事件");
};
}

render() {
return (
<div>
<button>按钮</button>
</div>
);
}
}

react 组件数据形式

props-外部传入

  1. 父组件通过属性绑定的形式绑定数据给子组件
  2. 子组件获取属性用{this.props.xxx}
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, { Component } from "react";

class Hello extends Component {
render() {
return (
<div>
{/* 子组件接收 */}
<p> {this.props.name} </p>
<p> {this.props.age} </p>
</div>
);
}
}

export default class App extends Component {
render() {
return (
<div>
{/* 父组件绑定数据 */}
<Hello name="yyc" age={18} />
</div>
);
}
}

props-自身定义

类组件通过一个关键字 static defaultProps = {} 来定义自身属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React, { Component } from "react";

export default class App extends Component {
// 通过一个关键字 static defaultProps = {} 来定义自身属性
static defaultProps = {
color: "pink",
};
render() {
const { color } = this.props;
return (
<div>
<p> {color} </p>
</div>
);
}
}

props-属性校验

  1. 需要第三方依赖包 prop-types
1
$ yarn add prop-types -S
  1. 代码部分
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
import React, { Component } from "react";
import PropTypes from "prop-types";

class Hello extends Component {
render() {
const { name, age } = this.props;
return (
<div>
<p> {name} </p>
<p> {age} </p>
</div>
);
}
}

Hello.propTypes = {
// prop是小写的p,引入是大写的
name: PropTypes.string,
age: PropTypes.number,
addProp(props, propName, componentName) {
// 在函数中可以属性校验
// console.log(props); // name,age 输出的是props属性
// console.log(propName); // addProp 输出的是函数名
// console.log(componentName); // Hello 输出的是组件名
if (props.age < 18) {
alert(" 未成年 ");
}
},
};

export default class App extends Component {
render() {
return (
<div>
<Hello name="yyc" age={17} />
</div>
);
}
}

state-两种定义形式

  1. 构造函数定义
  2. class 直接定义
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, { Component } from "react";

export default class App extends Component {
// state 定义方式一
// constructor() {
// super()
// this.state = {
// name: "yyc"
// }
// }

// state定义方式二
state = {
name: "yyc",
};

render() {
const { name } = this.state;
return (
<div>
<p> {name} </p>
</div>
);
}
}

setState-改变 state-案例

setState(()=>{},()=>{})
接收两个回调函数
第一个回调函数可以带有参数 pre,用于接收旧值

  1. 案例 1: 计数按钮
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
import React, { Component } from "react";

export default class App extends Component {
constructor() {
super();
this.state = {
// 定义数据
count: 1,
};
}

add = () => {
// 定义方法
this.setState((pre) => {
return {
count: pre.count + 1,
};
});
};

render() {
const { count } = this.state; // 从state中解构
return (
<div>
<button onClick={this.add}> + </button>
<p> {count} </p>
</div>
);
}
}
  1. 案例 2: 修改网页 title-合成事件-异步
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
import React, { Component } from "react";

export default class App extends Component {
constructor() {
super();
this.state = {
name: "张三",
};
}

// changeName = () => {
// this.setState(() => {
// return {
// name: "李四"
// }
// })
// document.title = this.state.name
// // 这里发现一个问题
// // 点击之后this.state.name 拿到的是旧值,
// // 分析:这里的setState是异步的,所以拿不到新值
// }

// setState可以传第二个参数,也是回掉函数,用于解决异步问题
changeName = () => {
this.setState(
() => {
return {
name: "李四",
};
},
() => {
document.title = this.state.name;
}
);
};

render() {
const { name } = this.state;
return (
<div>
<button onClick={this.changeName}>改变名字</button>
<p> {name} </p>
</div>
);
}
}
  1. 案例 3: 原生同步
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
import React, { Component } from "react";

export default class App extends Component {
constructor() {
super();
this.state = {
name: "张三",
};
}

componentDidMount() {
const btn = document.querySelector("button");
const _this = this;
btn.onclick = function () {
_this.setState({
name: "李四",
});
document.title = _this.state.name;
};
}

render() {
const { name } = this.state;
return (
<div>
<button onClick={this.changeName}>改变名字</button>
<p> {name} </p>
</div>
);
}
}

setState()在 render()中不能使用

render 函数中不能直接调用 setState
报错: 栈溢出(死循环)
原因: render 函数本身就是用于解析 this.state 和 this.props 的,解析时开始矛盾

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

export default class App extends Component {
constructor() {
super();
this.state = {
count: 1,
};
}
render() {
const { count } = this.state;
// this.setState({
// count: this.state.count + 1
// })
// 该注释代码会报错
return (
<div>
<p> {count} </p>
</div>
);
}
}

react-数据渲染

条件渲染

demo: 渲染一个标签, 在 react 中 {} 单花括号可以写 js 逻辑

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, { Component } from "react";

export default class App extends Component {
state = {
flag: true,
};

setFlag = () => {
this.setState({
flag: !this.state.flag,
});
};
render() {
const { flag } = this.state;
return (
<div>
<button onClick={this.setFlag}>按钮</button>
{
/* 使用了短路逻辑 */
flag && <p>你好</p>
}
</div>
);
}
}

列表渲染 + 数据请求 + 反向代理

  1. 列表渲染使用 map 语法, 也就是原生的数组方法, 使用该方法一定要是数组。
  2. cra-配置文件抽离-反向代理:
    • 目录路径 config/webpackDevServer.config.js
    • 找到 proxy
    • 书写反向代理
      1
      2
      3
      4
      5
      6
      proxy: {
      '/ajax': {
      target: 'https://m.maoyao.com',
      changeOrigin: true
      }
      },
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
import React, { Component } from "react";

export default class App extends Component {
state = {
list: null, // 设置空list
flag: false,
};

getList = () => {
this.setState({
flag: true,
});
fetch(
"/ajax/movieOnInfoList?token=&optimus_uuid=03700780983E11EBBCE74F727D62BDE1238C6477E95F4AE98875338903338692&optimus_risk_level=71&optimus_code=10"
)
.then((data) => data.json())
.then((res) => {
this.setState({
list: res.movieList,
flag: false,
});
})
.catch((error) => Promise.reject(error));
};
render() {
const { list, flag } = this.state;
return (
<div>
<button onClick={this.getList}>按钮</button>
{
/* 为数据请求过程中加个等待loading */
flag && <div>loading...</div>
}
<ul>
{
/* 因为一开始list为null 不是数组 没有map方法 所以用逻辑短路判断 */
list && list.map((item) => <li key={item.id}> {item.nm} </li>)
}
</ul>
</div>
);
}
}

路径别名

  1. 路径别名配置
    • 目录路径 config/webpack.config.js
    • 找到 alias c
    • 书写路径别名配置
      1
      2
      3
      4
      alias: {
      //别名: 磁盘路径
      'c': path.join(__dirname,'./src/components')
      }