React入门010之React的CSS方案

传统CSS使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<style>
#app{
color:red
}
</style>
</head>
<body>
<div id="app">App</div>
</body>
</html>

在后来 style 标签被提取到一个 css文件里

  • 也就是 内容 样式 分离 思想

React应用的CSS

index.html

1
2
3
4
5
6
7
<!DOCTYPE html>
<html lang="en">
<head> </head>
<body>
<div id="root"></div>
</body>
</html>

styles.css

1
.App {color: red;}

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react";
import ReactDOM from "react-dom";

// 引入样式
import "./styles.css";

function App() {
// react建议 class属性 使用 className 如果你不加可能依然可以运行。 建议就是按照react的建议做,不要自己作死。
return <div className="App">Hello World</div>;
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

这个代码好不好呢?

  • 在一开始没什么问题,但是当你的组件逐渐变多,你就觉得不好
1
2
3
比如组件嵌套的情况下
都有一个 class="title"
它们之间就会相互影响

styles.css

1
2
3
4
5
.title {
color: red;
border: 1px solid red;
padding: 10px;
}

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
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function Father() {
return (
<div className="title">
父亲组件
<Sun />
</div>
);
}
function Sun() {
return (
<div>
<div className="title">儿子组件</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Father />, rootElement);

两个组件都有title样式 我只想让 字体颜色变红

  • 结果 也变红了

唯一的办法就是给所有的组件加上一个前缀

1
2
.father-title{ ... }
.sun-title{ ... }

这个方式叫做—— BEM命名法(思路就是加前缀)

  • 为了防止各个组件之间的冲突,我们采用了加前缀的方式解决

styles.css

1
2
3
4
5
6
7
8
9
10
11
.father-title {
color: red;
border: 1px solid red;
padding: 10px;
}

.sun-title {
color: green;
border: 1px solid green;
padding: 10px;
}

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
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function Father() {
return (
<div className="father-title">
父亲组件
<Sun />
</div>
);
}
function Sun() {
return (
<div>
<div className="sun-title">儿子组件</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Father />, rootElement);

如果你想动态设置主题 theme

  • ES6 字符串的拼接来设置
1
2
3
4
5
6
7
8
function Sun(props) {
const theme = props.theme;
return (
<div>
<div className=`sun-title sun-${theme}`>儿子组件</div>
</div>
);
}

有没有一种方式让我写多个 class更加的简单一点?

  • 于是就有人发明了 classnames
  • google 搜 react classnames 得到一个 JedWatson写的库
  • https://codesandbox.io 上新建react项目
  • 添加依赖 搜索 classnames

styles.css

1
2
3
4
5
6
7
8
9
10
11
12
.App {
font-family: sans-serif;
text-align: center;
}

.red {
color: red;
}

.border10 {
border: 10px solid red;
}

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React from "react";
import ReactDOM from "react-dom";
import classNames from "classnames";
import "./styles.css";

function App() {
return (
<div className="App">
<h1 className={classNames("red", "border10")}>Hello CodeSandbox</h1>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

强迫症的心理(处女座)——模块化

每个组件对应一个目录 对应自己的css

1
2
3
4
5
6
7
8
|src
--|topBar
----|topBar.css
----|topBar.js
--|x
----|x.css
----|x.js
--|index.js

强迫症的执念——我就不想用前缀

  1. 两个title 自己分开
  2. css:做不到
  3. 前端:css go die !

既然你css做不到那你就靠边站,我用js

  • CSS in JS 方案

CSS in JS

简单来说这个方案大概有20种

  • google 搜索 github css in js
  • 你会搜到一个仓库 css-in-js
  • 里面收录了所有 css in js 的方案 那么有多少呢?
  • 大概 60多种,开不开心有60多种

此时有一个问题,为什么做前端架构很难呢?

  • 没有三年的项目经验
  • 没整过这些奇怪的库

有没有什么在前端一开始觉得很NB到后面变得很垃圾

  • grunt 三年前 三年后
  • angular 1.x

如何区分这些库呢?

  • 看star数量

筛选css in js 的方案

styled-components

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
import React from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
import "./styles.css";

const Button = styled.button`
border-radius: 3px;
border: 1px solid #666;
color: #333;
margin: 0 1em;
padding: 0.25em 1em;
`;

function App() {
return (
<div className="App">
<Button>按钮</Button>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 在线代码
  • 这样就解决了,你写前缀就浑身难受的心态
  • 强迫症是前端的原始动力
  • 而且你的世界从此没有了 .css文件

在写一个topbar.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
import React from "react";
import styled from "styled-components";

export default function Topbar(props) {
const theme = props.theme; // 'white | black'

const onClick = () => {
console.log(1);
};
const Logo = styled.div`
width: 40px;
height: 40px;
background: grey;
`;
const Nav = styled.nav`
height: 30px;
background: #333;
width: 200px;
color: white;
margin-left: 10px;
`;
const Wrapper = styled.div`
display: flex;
align-items: center;
border: 1px solid black;
padding: 10px;
background: ${theme};
`;
return (
<Wrapper>
<Logo>logo</Logo>
<Nav>nav</Nav>
</Wrapper>
);
}

此时你发现违背了 多年来前端的思想 内容样式分离

  • 又回到解放前了
  • react 说 我不仅内容混在一起 行为也要混在一起

styled-components

  • 目前react css比较火的方案
  • 可以再也看不到css文件
  • 不需要定义class
  • 而且从工程角度来说——这个方案对新人非常好,不会因为你改一个样式造成整个项目瘫痪了,你只需要关心自己的组件

emotion方案

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
/** @jsx jsx */
import React from "react";
import { jsx } from "@emotion/core";

export default function X(props) {
const theme = props.theme;
return (
<div
css={{
display: "flex",
alignItems: "center",
background: theme,
padding: 10
}}
>
<div
css={{
display: "inline-block",
color: "red",
fontSize: 20,
white: 40,
height: 40,
background: "grey"
}}
>
logo
</div>
<nav
css={{
width: 200,
height: 30,
background: "#333",
marginLeft: 10
}}
/>
</div>
);
}
  • 有个坑就是第一行必须写那个注释
  • 根据与 直接 style=”color:red;” 没区别
  • 强烈不推荐用

react-css-modules

我放弃了——为什么webpack还活着

如果你成功了,你就可以这样写样式了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './table.css';

class Table extends React.Component {
render () {
return <div styleName='table'>
<div styleName='row'>
<div styleName='cell'>A0</div>
<div styleName='cell'>B0</div>
</div>
</div>;
}
}

export default CSSModules(Table, styles);

radium

  • 看文档吧!
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 Radium from 'radium';
import React from 'react';
import color from 'color';

class Button extends React.Component {
static propTypes = {
kind: PropTypes.oneOf(['primary', 'warning']).isRequired
};

render() {
// Radium extends the style attribute to accept an array. It will merge
// the styles in order. We use this feature here to apply the primary
// or warning styles depending on the value of the `kind` prop. Since its
// all just JavaScript, you can use whatever logic you want to decide which
// styles are applied (props, state, context, etc).
return (
<button
style={[
styles.base,
styles[this.props.kind]
]}>
{this.props.children}
</button>
);
}
}

Button = Radium(Button);

// You can create your style objects dynamically or share them for
// every instance of the component.
var styles = {
base: {
color: '#fff',

// Adding interactive state couldn't be easier! Add a special key to your
// style object (:hover, :focus, :active, or @media) with the additional rules.
':hover': {
background: color('#0074d9').lighten(0.2).hexString()
}
},

primary: {
background: '#0074D9'
},

warning: {
background: '#FF4136'
}
};
  • 单独看例子就——反人类

总结四个CSS in JS 方案

1
2
3
4
5
6
7
8
9
10
11
1. styled-componends      
const Title = styled.div` 样式代码...`

2. emotion
<div css={对象}>

3. css-module
<div styleName="title"> 保证title不对其他组件有影响

4. radium
<div style={[style.title,style.base]}>

如果是我我觉得第三个可能是最好的!

回顾

  • 避免 同名 .title 冲突 加前缀
  • 不想加前缀,强迫症
  • css in js 得到 60多个方案
  • 按照star 选了 4个 来实现不写前缀

建议

1
2
3
4
5
6
7
8
9
10
11
12
13
如果写一个应用 app 
- 用 css-module
- 或者 styled-componends
因为它们还是在写 css

如果写一个 UI库 lib
- 使用旧的 css 方案 加前缀
- 因为上面的方式会生成随机名字,你的组件样式就没法被覆盖了


.title --> title_xd0031 这个名字是随机的,没法覆盖

.x-title --> .x-title 不帮你生成名字,可以进行改写和覆盖样式

React入门009之React生命周期

React生命周期

面试必考(但是平时用的不多)

VanillaJS理解一个div的生命周期

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
<body>
<div id="root"></div>
</body>
<script>
let app = document.getElementById('root');

// create 出现在js内存里
let div = document.createElement('div');
div.style.border = '1px solid red';

// mount 之前
let state = 0;
div.innerHTML = `<p>${state}</p>
<button>add</button>
<button>die</button>
`;

// mount 挂载
app.appendChild(div);



div.querySelector('button').onclick = ()=>{
state += 1
// update div
div.querySelector('p').innerText = state;
}

div.querySelectorAll('button')[1].onclick = ()=>{
// remove div 但是要移干净
// 移除事件,移除引用
div.querySelector('button').onclick = null
div.querySelectorAll('button')[1].onclick = null
div.remove()
div = null
// 此时才是真正的 destroy div
}
</script>

人有生/老/病/死
div有 create / mount / update / destroy (生 挂 更 死)

此时看React版的生命周期(完整版)

在线代码

  • constructor 创建
  • componentWillMount 将要挂载
  • render 填充/更新
  • componentDidMount 挂载完毕
  • componentWillUpdate 更新之前
  • shouldComponentUpdate 要不要更新
  • componentDidUpdate 更新之后
  • componentWillUnmount 没死之前
  • componentWillReceiveProps 父组件传递props了
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

let div = document.createElement("div");
document.body.appendChild(div);
console.log = function(content) {
div.innerHTML += `${content}<br>`;
};

class Baba extends React.Component {
constructor() {
super();
this.state = {
hasChild: true
};
}
onClick() {
this.setState({
hasChild: false
});
}
callSon() {
this.setState({
word: "你还好吧"
});
}
render() {
return (
<div>
我是你爸爸
<button onClick={() => this.onClick()}>kill son</button>
<button onClick={() => this.callSon()}>call son</button>
{this.state.hasChild ? <App word={this.state.word} /> : null}
</div>
);
}
}

class App extends React.Component {
onClick() {
console.log("用户点击了");
this.setState({
n: this.state.n + 1
});
}
updateX() {
this.setState({
x: this.state.x + "!"
});
}
constructor() {
super();
this.state = {
n: 0,
x: "不展示"
};
}
componentWillMount() {
console.log("将要 mount App");
}
render() {
// update
console.log("填充/更新 App 的内容");
return (
<div className="App">
{this.state.n}
<br />
我爸说 {this.props.word}
<br />
<button onClick={() => this.onClick()}>+1</button>
<button onClick={() => this.updateX()}>update x</button>
</div>
);
}
componentDidMount() {
console.log("mount App 完毕");
}
componentWillUpdate() {
console.log("update App 之前");
}
shouldComponentUpdate(nextProps, nextState) {
console.log("要不要更新呢?");
if (this.state.n === nextState.n) {
return false;
} else {
return true;
}
}
//update is render
componentDidUpdate() {
console.log("update App 之后");
}
componentWillUnmount() {
console.log("App 快要死了,记得喂狗");
}
componentWillReceiveProps() {
console.log("我爸说话了");
}
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Baba />, rootElement);

我们在页面一般做什么?

  1. ajax请求数据
  2. 更新数据 setState
  3. 事件监听 onClick
  4. 初始化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
什么时候请求数据呢? 理论上越早越好,任何周期都可以发请求
- 创建的时候 (推荐)
- 用户点击事件触发的时候 (推荐)
- 组件将要 mount 的时候
- mount之后


什么时候初始化呢?
- 可以在 constructor (推荐)
- 可以在 componentWillMount 里 (不推荐)
- render呢? 可以 但是要在return之前 因为此时要用到 state 了


什么时候更新 state呢?
- 在 constructor 创建state之后 setState 不可以报错 Can't call setState on a component that is not yet mounted.
- 不能在 render 里 setState() 这样就死循环了 因为 setState会触发 render
- 不能在 mount 之前 也不能在 render 里 setState
- 为什么在 componentWillUpdate 不能 因为setState会触发 将要更新了 而此时 setState 死循环
- componentWillUnmount 可以 但是在它死之前 没意义啊
- 一般在 componentWillReceiveProps / componentWillMount / 事件监听里


是否决定更新的函数
- shouldComponentUpdate 返回 return false 就不更新
- 极其特殊的情况才用—— 一般用不到

面试

面试:什么情况下用 shouldComponentUpdate 允许我们

手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新

1.请说出React所有生命周期函数

2.请问 shouldComponentUpdate 为什么那么重要

  • 可以自定义是否更新组件
  • 优化更新的效率

3.请问ajax在什么时候用?

  • componentDidMount 原因是 constructor 里不能用 setState
  • 在 fb里 多次调用会造成不稳定 (其实很多人也不知道为什么fb会造成不稳定)

4.用户点击按钮修改state会触发那些周期函数

  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

React入门008之Router

先来一个选项卡

  • 打开 https://codesandbox.io/ 选择react
  • 注意我用的react版本是16.7
    1
    2
    3
    4
    5
    "dependencies": {
    "react": "16.7.0-alpha.0",
    "react-dom": "16.7.0-alpha.0",
    "react-scripts": "2.0.3"
    },

style.css

1
2
3
4
5
6
7
.box {
border: 1px solid #ccc;
text-align: center;
padding: 100px 0;
width: 90%;
margin: 0 auto;
}

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
import React from "react";
import ReactDOM from "react-dom";
import { useState } from "react";
import "./styles.css";

function Login() {
return <div className="box">登录</div>;
}

function Register() {
return <div className="box">注册</div>;
}

function App() {
let [ui, setUi] = useState("登录");
let onClickLogin = () => setUi("登录");
let onClickRegister = () => setUi("注册");
return (
<div className="App">
<button onClick={onClickLogin}>login</button>
<button onClick={onClickRegister}>register</button>
<div>{ui === "登录" ? <Login /> : <Register />}</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

需求来了,我现在想把链接分享给别人,如分享注册页面,结果链接打开还是登录

使用hash做路由

1
hash 就是锚点  就是url后面有个 '#'+path
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
import React from "react";
import ReactDOM from "react-dom";
import { useState } from "react";
import "./styles.css";

function Login() {
return <div class="box">登录</div>;
}

function Register() {
return <div class="box">注册</div>;
}

function App() {
let hash = window.location.hash;
// hash默认是带 '#'号的
let initUi = hash === '#register'?'注册':'登录';

let [ui, setUi] = useState("登录");
let onClickLogin = () => {
setUi("登录");
window.location.hash = 'login'
};
let onClickRegister = () => {
setUi("注册");
window.location.hash = 'register'
};
return (
<div className="App">
<button onClick={onClickLogin}>login</button>
<button onClick={onClickRegister}>register</button>
<div>{ui === "登录" ? <Login /> : <Register />}</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

以上我们完成了

  • 用户根据 hash 值来进入对应页面
  • 虽然url有点丑,先忍着吧!

除了 hash 还能用什么

  • window.location.pathname

location.pathname 的问题

  • 修改路径会造成页面刷新
  • 如果后台不进行url重定向(单页应用url指向index.html) 就会造成 404

不刷新修改页面路径的API——pushstate API

1
2
window.history.pushState(null,'','/login')
window.history.pushState(null,'','/register')

虽然这样改不造成页面刷新

但是

三种方式

1
2
3
4
5
6
7
8
9
10
11
12
window.location.hash = // 让url变化同时不刷新页面


window.location.pathname = // 会刷新页面 ,基本不考虑
x 肯定不用这个

window.history.pushState(null,'',path) // 不刷新页面,修改路径
? 这个是不确定的
因为有两种情况
1. 后端会将所有路径指向首页 就可以这样做
2. 后端傻X 那你就放弃吧
3. 既然后端傻X,那你就学node自己做后端控制url 抢他饭碗

什么叫url指向首页

1
2
3
4
5
6
7
8
你可以在codesendbox里 创建一个react项目 然后在浏览器的路径后随便修改
输入如下路径都进入首页

https://www.xxx.com/xxx
https://www.xxx.com/bbb
https://www.xxx.com/ccc
https://www.xxx.com/fuck
https://www.xxx.com/shit

最稳妥的方式是用 hash 100%没问题
最不靠谱的是用 pathname 因为会刷新页面
有个靠谱后端的情况下 用 pushState 或者你会nodeJs自己 控制url指向 index.html

用pushState实现路由

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
import React from "react";
import ReactDOM from "react-dom";
import { useState } from "react";
import "./styles.css";

function Login() {
return <div className="box">登录</div>;
}

function Register() {
return <div className="box">注册</div>;
}

function App() {
let path = window.location.pathname;
// hash默认是带 '#'号的
let initUi = path === "/register" ? "注册" : "登录";
console.log(initUi);
let [ui, setUi] = useState(initUi);
let onClickLogin = () => {
setUi("登录");
history.pushState(null, "", "login");
};
let onClickRegister = () => {
setUi("注册");
history.pushState(null, "", "register");
};
return (
<div className="App">
<button onClick={onClickLogin}>login</button>
<button onClick={onClickRegister}>register</button>
<div>{ui === "登录" ? <Login /> : <Register />}</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

分析:现在可以解决路由问题了

新的问题:无穷多个路由的时候怎么办

1
2
3
4
5
6
7
8
9
10
11
12
13
- login
- register
- index
- trade
- user // 用户列表
- user/1 // 用户id为1的
- user/2 // 用户id为2的
- ...
- user/100 //用户id为100的

此时

if else 明显不够用

使用库ReactRouter

ReactRouter

初始化依赖(如果你用的create-react-app命令)

1
2
3
4
5
npm install -g create-react-app
create-react-app demo-app
cd demo-app

npm install react-router-dom

如果你用的 codesendbox.io 那就直接安装依赖 react-router-dom

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
import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

const Index = () => <h2>Home</h2>;
const About = () => <h2>About</h2>;
const Users = () => <h2>Users</h2>;

function App() {
return (
<div className="App">
<Router>
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about/">About</Link>
</li>
<li>
<Link to="/users/">Users</Link>
</li>
</ul>
</nav>

<Route path="/" exact component={Index} />
<Route path="/about/" component={About} />
<Route path="/users/" component={Users} />
</div>
</Router>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

唯一的坑在这里

1
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

如果是你? 你搜索后会选择去中文文档还是英文的?

1
2
3
中文——给你个例子——死活运行不了

英文——照着抄 (少走弯路)

React入门007之Hooks

Hooks

React新的API最新支持Hooks版本为16.7,需要单独安装

那么开始

  1. https://codesandbox.io/上新建一个react项目
  2. 在Dependencies上能看到React的版本(hooks的支持要求16.7.0-alpha)
  3. 修改package.json

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     "dependencies": {
    "react": "16.7.0-alpha",
    "react-dom": "16.7.0-alpha",
    "react-scripts": "2.0.3"
    },

    //此时保存报错了
    // 在命令行查看react的所有版本
    npm info react versions
    // 查看最新版本
    npm info react versions

    //再次修改 package.json

    "dependencies": {
    "react": "16.7.0-alpha.2",
    "react-dom": "16.7.0-alpha.2",
    "react-scripts": "2.0.3"
    },
  4. 开始写我们的react应用 index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import React from "react";
    import ReactDOM from "react-dom";

    function App() {
    return <div className="App">hello</div>;
    }

    const rootElement = document.getElementById("root");
    ReactDOM.render(<App />, rootElement);

useState使用

有状态的函数组件

16.7以前只有class组件有状态,16.7开始,函数是可以有状态的

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
import React from "react";
import ReactDOM from "react-dom";

// step01 引入 useState
import { useState } from "react";

function App() {
// 这样定义肯定不行
// let count = 0;
// let add = function(){}

// 正确定义状态
const [count, setCount] = useState(0); //count 初始值是 0
const add = () => {
setCount(count + 1);
};

const minus = () => {
setCount(count - 1);
};

return (
<div>
<div>{count}</div>
<div>
<button onClick={add}>+1</button>
<button onClick={minus}>-1</button>
</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

代码

核心代码

1
2
3
4
5
6
7
8
const [ 
count, // 值
setCount // 更新函数
] = useState(0);

// useState 函数会返回一个数组
// 数组第一项表示 count的值
// 数组第二项表示 count的更新函数

自从有了useState之后,就不需要一个class去维护状态

  • 直接 useState(初始值)
  • setCount(count+1) / setCount(count -1) 就可以改变状态

而如果用之前的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
26
27
28
29
30
31
// 用 class写组件
class App2 extends React.Component {
constructor() {
super();
this.state = {
count: 0
};
}
add() {
this.setState({
count: this.state.count + 1
});
}
minus() {
this.setState({
count: this.state.count - 1
});
}
render() {
return (
<div>
app2
<div>{this.state.count}</div>
<div>
<button onClick={this.add.bind(this)}>+1</button>
<button onClick={this.minus.bind(this)}>-1</button>
</div>
</div>
);
}
}

结论就是:

  • 用了hooks 之后语法简介了很多
  • 再也不用class的方式写组件了
  • 注意要将react升级到16.7.0
  • 同时这里可以很爽的给数据赋值,不用setState了

useState的更多例子

复杂对象如何修改值?

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 from "react";
import ReactDOM from "react-dom";

// step01 引入 useState
import { useState } from "react";

function App() {
const [count, setCount] = useState(0); //count 初始值是 0
const [user, setUser] = useState({ name: "hjx", age: 18 });

const add = () => {
setCount(count + 1);
};
const minus = () => {
setCount(count - 1);
};

const addAge = () => {
setUser({
...user,
age: user.age + 1
});
};
return (
<div>
<div>{count}</div>
<div>
<button onClick={add}>+1</button>
<button onClick={minus}>-1</button>
<hr />
userInfo:姓名 {user.name} 年纪{user.age}
<button onClick={addAge}>age++</button>
</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

user添加爱好 hobbies

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
import React from "react";
import ReactDOM from "react-dom";

// step01 引入 useState
import { useState } from "react";

function App() {
const [count, setCount] = useState(0); //count 初始值是 0
const [user, setUser] = useState({
name: "hjx",
age: 18,
hobbies: ["火影", "编程", "王者"]
});

const add = () => {
setCount(count + 1);
};
const minus = () => {
setCount(count - 1);
};

const addAge = () => {
setUser({
...user,
age: user.age + 1
});
};
const addHobbie = () => {
let num = parseInt(Math.random() * 100);
setUser({
...user,
hobbies: [...user.hobbies, num]
});
};

return (
<div>
<div>{count}</div>
<div>
<button onClick={add}>+1</button>
<button onClick={minus}>-1</button>
<hr />
userInfo:姓名 {user.name} 年纪{user.age}
<button onClick={addAge}>age++</button>
<hr />
爱好:{user.hobbies.join(",")}
<button onClick={addHobbie}>Hobbie++</button>
</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

删除 hobbies第二项怎么处理

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
57
58
59
60
61
62
63
import React from "react";
import ReactDOM from "react-dom";

// step01 引入 useState
import { useState } from "react";

function App() {
const [count, setCount] = useState(0); //count 初始值是 0
const [user, setUser] = useState({
name: "hjx",
age: 18,
hobbies: ["火影", "编程", "王者"]
});

const add = () => {
setCount(count + 1);
};
const minus = () => {
setCount(count - 1);
};

const addAge = () => {
setUser({
...user,
age: user.age + 1
});
};
const addHobbie = () => {
let num = parseInt(Math.random() * 100);
setUser({
...user,
hobbies: [...user.hobbies, num]
});
};

const removeHobbie = () => {
user.hobbies.splice(1, 1);
setUser({
...user,
hobbies: [...user.hobbies]
});
};

return (
<div>
<div>{count}</div>
<div>
<button onClick={add}>+1</button>
<button onClick={minus}>-1</button>
<hr />
userInfo:姓名 {user.name} 年纪{user.age}
<button onClick={addAge}>age++</button>
<hr />
爱好:{user.hobbies.join(",")}
<button onClick={addHobbie}>Hobbie++</button>
<button onClick={removeHobbie}>删除第二个Hobbie</button>
</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

以上就是hooks的基本使用

疑问?能不能将 const [count, setCount] = useState(0) 放在函数组件外

  • 这是API没有告诉我们的,会报错
  • 没有为什么,就是不准

疑问2 能不能修改 const [count, setCount] = useState(0) 名字

  • 可以
1
2
3
4
const [x, y] = useState(0)
const add = () => {
y(x + 1);
}

useEffect使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//没有作用的函数
function fn(){}


// 有作用的函数
function fn2(){ console.log(1) }

// 有作用的函数
function fn3(a,b){ return a + b }


// fn2 和 fn3 的区别
// fn2会让人有疑问 哪里来的 console
// fn3 不会让人有 a,b是哪里来的感觉

如果你在函数里看到了 不知道从哪里来的东西 这个函数就是有副作用的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 来看什么是副作用
function fn2(){ console.log(1) }

fn2() //打印 1

fn2() //打印 1

console.log = function(){ }

fn2() //什么都不打印

// 如果你依赖的东西不知道从哪里来的
// 如果这个东西突然有一天被人改了,你也不知道
// 这就是 有副作用的函数的问题

// 再看 fn3 无论你怎么修改 都是返回 a+b的结果
// fn3就叫做——纯函数 (不依赖外部不知道哪里来的东西的函数)

什么情况下用 effect

在 index.html 里添加一个节点元素 id=box1

1
2
<div id="box1" style="border:1px solid red;"></div>
<div id="root"></div>

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
import React from "react";
import ReactDOM from "react-dom";

// step01 引入 useState
import { useState, useEffect } from "react";

function App() {
const [count, setCount] = useState(0); //count 初始值是 0

const add = () => {
setCount(count + 1);
};

useEffect(() => {
// 你不知道 box1是从哪里来的
document.querySelector("#box1").innerText = count;
});

return (
<div>
<div>{count}</div>
<div>
<button onClick={add}>+1</button>
</div>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React入门006之ContextApi03换肤功能

换肤功能

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
51
52
53
54
55
56
57
58
59
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

const themeContext = React.createContext();

function Button() {
return <button>button</button>;
}

function Input() {
return <input />;
}

function Box(props) {
return <div className={`box ${props.theme}`}>{props.children}</div>;
}

class App extends React.Component {
constructor() {
super();
this.state = {
theme: "light"
};
}
change() {
let res = this.state.theme === "green" ? "red" : "green";
this.setState({
theme: res
});
}
render() {
return (
<themeContext.Provider value={this.state.theme}>
<div className="App">
<button onClick={this.change.bind(this)}>换肤</button>
<themeContext.Consumer>
{theme => {
return (
<div>
<Box theme={theme}>
<Button />
</Box>
<Box theme={theme}>
<Input />
</Box>
</div>
);
}}
</themeContext.Consumer>
</div>
</themeContext.Provider>
);
}
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

style.css

1
2
3
4
5
6
7
8
9
10
11
12
.App {
font-family: sans-serif;
text-align: center;
}

.box.green {
border: 1px solid green;
}

.box.red {
border: 1px solid red;
}

React入门006之ContextApi02

继续上一篇内容

ContextApi

如果我想修改传递的n呢?

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function F1() {
return (
<div className="box">
1111
<F2 />
</div>
);
}
function F2() {
return (
<div className="box">
2222
<F3 />
</div>
);
}
function F3() {
return (
<div className="box">
3333
<nContext.Consumer>
{xxx => <F4 n4={xxx.n} setN={xxx.setN} />}
</nContext.Consumer>
</div>
);
}
function F4(props) {
return (
<div className="box">
4444,{props.n4}
<button onClick={props.setN}>Click ME</button>
</div>
);
}

const nContext = React.createContext();

class App extends React.Component {
constructor() {
super();
this.state = {
x: {
n: 100,
setN: () => {
this.setState({
x: {
...this.state.x,
n: this.state.x.n + 1
}
});
}
}
};
}
render() {
return (
<div>
<nContext.Provider value={this.state.x}>
<F1 />
</nContext.Provider>
</div>
);
}
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

以前我们传递值

  • 需要一层一层的传递

现在有了 ContextAPI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//在根节点包裹一个 nContext
return (
<div>
<nContext.Provider value={obj}>
<F1 />
</nContext.Provider>
</div>
);

/*
obj{
store,
setStore()
}
*/

// 后代任意层级想要使用 只需要包裹一个
<nContext.Consumer>
{(obj)=>{return <div>{obj}</div>}}
</nContext.Consumer>

React入门006之ContextApi

预习内容

在线IDE

先看代码

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
function f1() {
console.log(1);
f2();
}
function f2() {
console.log(2);
f3();
}
function f3() {
console.log(3);
f4();
}
function f4() {
console.log(4);
}
f1();
console.log("done");

/*
1
2
3
4
done
*/

再看

f1多了一个 参数 n1
我想要后面的每一个函数都能获取这个 n1
咋办?

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
function f1(n1) {
console.log(1,n1);
f2(n1);
}
function f2(n2) {
//这里的 n2就是 f1里的 n1
console.log(2,n2);
f3(n2);
}
function f3(n3) {
console.log(3,n3);
f4(n3);
}
function f4(n4) {
console.log(4,n4);
}

{
let n = 100;
f1( n );
console.log("done");
}
/*
函数里的函数 得到n
现在则需要 层层传递
*/

react 4层组件里实现上面的逻辑

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
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function F1(props) {
return (
<div>1111,{props.n1}
<F2 n2={props.n1}></F2>
</div>
);
}
function F2(props) {
return (
<div>2222,{props.n2}
<F3 n3={props.n2}></F3>
</div>
);
}
function F3(props) {
return (
<div>3333,{props.n3}
<F4 n4={props.n3}></F4>
</div>
);
}
function F4(props) {
return (
<div>4444,{props.n4}
</div>
);
}
class App extends React.Component {
constructor() {
super();
this.state = {
n: 99
};
}
render() {
return (
<div>
<F1 n1={this.state.n}/>
</div>
);
}
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

这样很麻烦需要层层传递

如果 F4想单独拿到 n呢?

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
// 唯一的办法将 n提取到外部
function f1() {
console.log(1);
f2();
}
function f2() {
//这里的 n2就是 f1里的 n1
console.log(2);
f3();
}
function f3() {
console.log(3);
f4();
}
function f4() {
console.log(4,n);
}

let n = 100;
{
f1( n );
console.log("done");
}
/*
f4单独 得到n 不想层层传递
现在则需要 将 n放在全局
*/

如果将 n 放在全局就回导致容易被篡改

  • 全局变量慎用

改良: 局部的全局变量

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
{
let x = {};
window.setX = function(key,value){
x[key] = value
}

window.f1 = function f1() {
console.log(1);
f2();
}

window.f2 = function f2() {
//这里的 n2就是 f1里的 n1
console.log(2);
f3();
}

window.f3 = function f3() {
console.log(3);
f4();
}

window.f4 = function f4() {
console.log(4,x['n']);
}
}

{
window.setX('n',100)
setTimeout(() => {
window.f1();
}, 1000);
console.log("done");
}
  • 这个 x 就叫 context
  • setX 就是 setContext
  • 他就是对于组件来使用的——局部的全局变量
  • 避免了 层层传递props

用Context 改写 React例子

style.css

1
2
3
4
.box {
border: 1px solid red;
padding: 10px;
}

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
import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function F1() {
return (
<div className="box">
1111
<F2 />
</div>
);
}
function F2() {
return (
<div className="box">
2222
<F3 />
</div>
);
}
function F3() {
return (
<div className="box">
3333
<nContext.Consumer>{xxx => <F4 n4={xxx} />}</nContext.Consumer>
</div>
);
}
function F4(props) {
return <div className="box">4444,{props.n4}</div>;
}

const nContext = React.createContext();

class App extends React.Component {
render() {
return (
<div>
<nContext.Provider value="999">
<F1 />
</nContext.Provider>
</div>
);
}
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

理解代码

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
40
41
42
43
44
45
46
47
48
49
<div></div> 
//翻译成
React.createElement('div',null)


// ------------------------------------
<div className="x" title="hi"></div>
//翻译成
React.createElement('div',{className:'x',title:'hi'})


// ------------------------------------
<div className="x" title="hi">aaaa</div>
//翻译成
React.createElement('div',{className:'x',title:'hi'},'aaaa')


// ------------------------------------
function F1(){
return '1111'
}
<div className="x" title="hi">
<F1></F1>
</div>

//翻译成 当你用标签写的时候 永远会在 前面加上一个React.createElement
React.createElement(
'div',{className:'x',title:'hi'},
React.createElement(F1,null)
)

// ------------------------------------
function F1(){
return '1111'
}
<div className="x" title="hi">
F1
{F1}
<F1/>
</div>
//翻译成
//当你写F1就是 字符串 "F1"
//当你用标签写的时候 永远会在 前面加上一个React.createElement
React.createElement(
'div',{className:'x',title:'hi'},
'F1',
F1, //这是个函数 但是React并不知道怎么用
React.createElement(F1,null)
)

实现一个接受函数的组件——(Consumer原理)

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
import React from "react";
import ReactDOM from "react-dom";

// Ccc就是 Consumer
function Ccc(props) {

/*
props.children 可以获取 Ccc里得内容 <Ccc>{fn}<Ccc/>
如果 传递一个函数
props.children 就是那个函数 fn
传递 <Ccc>{fn}{fn2}<Ccc/>
那么 props.children 就是 [fn,fn2]
*/

let info = 100;
let result = props.children(info);
return <div>{result}</div>;
}

function App() {
return (
<div>
<Ccc c1="c1">
{msg => {
console.log(`我得到的信息是${msg}`);
return <div>我得到的数据是:{msg}</div>;
}}
</Ccc>
</div>
);
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

React入门005之Redux是什么03

React-Redux

核心就几个东西

  • Provider
  • connect
  • connectAdvanced

我直接拿上一篇的例子复制了一份目录在

  • demo-react-redux-2
1
2
# 安装 react-redux
yarn add react-redux

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
51
52
53
54
55
56
57
58
59
60
61
62
// step1 引入Provider
// step2 用 Provider 包裹App 并传递 store


import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { Provider } from 'react-redux'

import {createStore} from 'redux';
const stateChanger = (state,action)=>{
if (state === undefined){
return 0;
}else{
if(action.type === 'add'){
var newState =state + action.payload;
return newState;
}else{
return state;
}
}
return newState;
}
const store = createStore(stateChanger)

render();

//render不传递参数
store.subscribe(render)

/*
//render传递参数
store.subscribe(()=>{
render(参数)
})
*/

function add3(){
if(store.getState()%2 === 1){
store.dispatch({type:'add',payload: 1})
}
}

function add4(){
setTimeout(() => {
store.dispatch({type:'add',payload: 1})
}, 2000);
}

function render(){
ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('root'));

}

serviceWorker.unregister();

普及基础知识

1
2
3
4
5
6
7
8
9
10
// 这是啥意思?  一个函数返回值是函数
function xx(a){
console.log(a)
return function(b){
console.log(a + b)
}
}

xx(1)(2);
// xx()叫做 偏函数 (参数不全)

App.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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// step1 引入 connect
// step2 connect()(App) 传递App给 connect
import React, { Component } from 'react';

import { connect } from 'react-redux'
class App extends Component {
render() {
console.log(this.props);
return (
<div className="App">
<div>
你点击了<span id="value">{this.props.n}</span>次
<div>
<button id="add1" onClick={()=>this.props.add1()}>+1</button>
<button id="add2">+2</button>
<button id="add1IfOdd">如果单数就+1</button>
<button id="addAfter2Ses">2秒后+1</button>
</div>
</div>
</div>
);
}
}

/*
这个 x 就是 把 store里的东西拿出来 放到 this.props上
所以如果 store = {n:0}传递到这里
就变成了 this.props.n = 0
function x(state){
return{
n:state.n
}
}
*/
// 映射 state===>props
/*
// mapStateToProps 必须是一个函数
接受一个参数 state 还有一个不常用的参数
*/
function mapStateToProps(state){
return{
n:state.n
}
}

// 如何 add 呢? 产生一个action 通过这个connect你就不用 store.dispatch
// 把dispatch映射到 Props
/*
mapDispatchToProps
可以是个函数也可以是个对象
function mapDispatchToProps(dispatch){
return {
add1:()=>dispatch({type:'add',payload:1})
}
}
*/

const mapDispatchToProps = {
add1:()=>{
return {type:'add',payload:1}
}
}

export default connect(mapStateToProps,mapDispatchToProps)(App);

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
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { Provider } from 'react-redux'

import {createStore} from 'redux';
const stateChanger = (state,action)=>{
if (state === undefined){
return {n:0}
}else{
if(action.type === 'add'){
var newState ={n:state.n + action.payload}
return newState;
}else{
return state;
}
}
}
const store = createStore(stateChanger)

ReactDOM.render(
<Provider store={store}>
<App/>
</Provider>
, document.getElementById('root'));

serviceWorker.unregister();

代码仓库

React入门005之Redux是什么02

Redux+React

安装 react脚手架工具

1
2
3
4
5
6
7
8
9
10
npm i -g create-react-app@2.1.1

# 新建目录
mkdir demo-react-redux-1
# 切换目录下
cd demo-react-redux-1
# 当前目录新建一个 生成一个react项目
create-react-app .
# 安装成功
yarn start

编辑 app.js

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

class App extends Component {
render() {
return (
<div className="App">
<div>
你点击了<span id="value">0</span>次
<div>
<button id="add1">+1</button>
<button id="add2">+2</button>
<button id="add1IfOdd">如果单数就+1</button>
<button id="addAfter2Ses">2秒后+1</button>
</div>
</div>
</div>
);
}
}

export default App;

实现我们上一篇的雏形

引入 redux

1
2
3
4
5
6
7
8
# 踩坑了
npm i redux
# 因为我们默认使用 yarn install的

# 如果你混着用 就会导致重新 安装所有依赖

# 所以 还是 用yarn
yarn add redux

渲染 store 

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
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import {createStore} from 'redux';
const stateChanger = (state,action)=>{
if (state === undefined){
return 0;
}else{
if(action.type === 'add'){
var newState =state + action.payload;
return newState;
}else{
return state;
}
}
return newState;
}
const store = createStore(stateChanger)

// 根节点传递 store
ReactDOM.render(<App value={store.getState()}/>, document.getElementById('root'));

serviceWorker.unregister();

App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// props使用 根结点传递的store
import React, { Component } from 'react';

class App extends Component {
render() {
return (
<div className="App">
<div>
你点击了<span id="value">{this.props.value}</span>次
<div>
<button id="add1">+1</button>
<button id="add2">+2</button>
<button id="add1IfOdd">如果单数就+1</button>
<button id="addAfter2Ses">2秒后+1</button>
</div>
</div>
</div>
);
}
}
export default App;

新问题 点击 add的问题

  • store是从props里取的 在App.js里根本拿不到
  • 只能继续从 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
22
23
24
25
26
27
28
29
30
31
32
33
import React, { Component } from 'react';

class App extends Component {
add1(){
this.props.onAdd1();
}
add2(){
this.props.onAdd2();
}
add3(){
this.props.onAdd3();
}
add4(){
this.props.onAdd4();
}
render() {
return (
<div className="App">
<div>
你点击了<span id="value">{this.props.value}</span>次
<div>
<button id="add1" onClick={()=>this.add1()}>+1</button>
<button id="add2" onClick={()=>this.add2()}>+2</button>
<button id="add1IfOdd" onClick={()=>this.add3()}>如果单数就+1</button>
<button id="addAfter2Ses" onClick={()=>this.add4()}>2秒后+1</button>
</div>
</div>
</div>
);
}
}

export default App;

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
51
52
53
54
55
56
57
58
59
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import {createStore} from 'redux';
const stateChanger = (state,action)=>{
if (state === undefined){
return 0;
}else{
if(action.type === 'add'){
var newState =state + action.payload;
return newState;
}else{
return state;
}
}
return newState;
}
const store = createStore(stateChanger)

render();

//render不传递参数
store.subscribe(render)

/*
//render传递参数
store.subscribe(()=>{
render(参数)
})
*/

function add3(){
if(store.getState()%2 === 1){
store.dispatch({type:'add',payload: 1})
}
}

function add4(){
setTimeout(() => {
store.dispatch({type:'add',payload: 1})
}, 2000);
}

// 传递每个修改store的处理函数,因为App.js里 没法获取操作 store
function render(){
ReactDOM.render(
<App value={store.getState()}
onAdd1={()=>{store.dispatch({type:'add',payload: 1})}}
onAdd2={()=>{store.dispatch({type:'add',payload: 2})}}
onAdd3={add3}
onAdd4={add4}
/>, document.getElementById('root'));

}

serviceWorker.unregister();

对比 Redux+VanillaJS 和 Redux+React

1
2
3
Redux + VanillaJS 每次点击按钮 整体 render

Redux + React 每次点击按钮 只更新包含数字的span
  • 这就是React的好处 让你每次不需要整体更新div,它通过dom diff 找该更新的地方

尽管如此 Redux+React的写法依然很恶心

  • 需要根节点把action的函数传递下去
1
2
3
4
5
6
7
8
9
10
11
12
13
// index.js里
<App value={store.getState()}
onAdd1={()=>{store.dispatch({type:'add',payload: 1})}}


// App.js里
class App extends Component {
add1(){
this.props.onAdd1();
//为什么不能直接在这里 派发action呢?
// store.dispatch()
}
}

有一个办法就是在根节点传递 store

1
2
3
4
5
6
<App value={store.getState()}
store={store}
/>

//这样 App.js里就可以
store.dispatch()

但是

但是

但是

虽然不用一层一层的往上调用,但是需要一层一层的往下传递store

假设 App内还有组件 就要每次都往子元素里传递

解决方案 React-Redux

注意是 React-Redux 跟 Redux是不同的

详情见下一篇

代码仓库

React入门005之Redux是什么01

Redux

一个不错的Redux中文文档

- 建议直接看

示例

里面有个 Counter Vanilla示例

  • Vanilla是什么 (英文意思香草)

what

VanillaJS就代表 原生JS

因为有很多前端程序员上来就学框架,根本不懂底层原理只会用库或框架,于是很多人上手就学vue,此时让它用 原生他不会,于是有人为了嘲讽这些不会原生JS的人 发明了一个新的框架就叫 VanillaJS

Counter Vanilla就是 没有用任何的库(用redux结合原生JS)

我们的需求 实现几个按钮点击触发+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
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdn.bootcss.com/redux/4.0.1/redux.min.js"></script>
<div id="app"></div>
<script>
function render(){
var app = document.querySelector('#app');
app.innerHTML = `
<div>
你点击了<span id="value">0</span>次
<div>
<button id="add1">+1</button>
<button id="add2">+2</button>
<button id="add1IfOdd">如果单数就+1</button>
<button id="addAfter2Ses">2秒后+1</button>
</div>
</div>
`;
}
render();
</script>
</body>
</html>

用redux怎么做呢?

根据示例

  1. 我们第一步要创建一个store 存这个0
  2. 这个store需要接受一个参数(更新store的函数)。
  3. 这个参数是一个函数

    1
    2
    3
    4
    5
    6
    7
    8
    (接受两个参数)
    第一个是上一次 store的状态
    第二个是 具体的操作

    var store = Redux.createStroe(函数xx)
    function 函数xx(之前的状态,操作){
    return 新的状态
    }
  4. 这个函数是用来更新store的

  • step1 dispatch 派发一个action
  • step2 根据操作生成新的 state 触发一个事件
  • step3 接受 事件重新 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdn.bootcss.com/redux/4.0.1/redux.min.js"></script>
<div id="app"></div>
<script>
function add1(){
// step1 dispatch 派发一个action
// 触发一个事件
// 派发一个动作
store.dispatch({type:'add'})
}

function render(store){
var app = document.querySelector('#app');
app.innerHTML = `
<div>
你点击了<span id="value">${store.getState()}</span>次
<div>
<button id="add1" onclick="add1()">+1</button>
<button id="add2">+2</button>
<button id="add1IfOdd">如果单数就+1</button>
<button id="addAfter2Ses">2秒后+1</button>
</div>
</div>
`;
}

function stateChanger(state,action){
if(state === undefined){
return 0;
}else{
if(action.type === 'add'){
var newState = state + 1;
return newState;
// step2 根据操作生成新的 state 触发一个事件
}else{
return state;
}
}
}
//初始化 store
var store = Redux.createStore(stateChanger)

render(store);

//监听 store的变化 然后触发render
store.subscribe(()=>{

// step3 接受 事件重新 render
render(store)

})
</script>
</body>
</html>

store 和 state的关系

1
2
3
4
5
state    ------> store存储了 state
获取 state ------> store.getState()

store ------> Redux.create(函数)
更新 state -----> 触发 action 也就是 dispatch({type:'操作'})

完善其他按钮的功能

  • 新增payload使用
  • 异步调用 action
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
57
58
59
60
61
62
63
64
65
66
67
68
69
<!DOCTYPE html>
<html lang="en">
<body>
<script src="https://cdn.bootcss.com/redux/4.0.1/redux.min.js"></script>
<div id="app"></div>
<script>
function add1(){
store.dispatch({type:'add',payload:1})
}
function add2(){
store.dispatch({type:'add',payload:2})
}

function add1IfOdd(){
var oldState = store.getState()
if(oldState % 2 ===1){
store.dispatch({type:'add',payload:1})
}
}

function addAfter2Ses(){
setTimeout(() => {
store.dispatch({type:'add',payload:1})
}, 2000);
}

function render(store){
var app = document.querySelector('#app');
app.innerHTML = `
<div>
你点击了<span id="value">${store.getState()}</span>次
<div>
<button id="add1" onclick="add1()">+1</button>
<button id="add2" onclick="add2()">+2</button>
<button id="add1IfOdd" onclick="add1IfOdd()">如果单数就+1</button>
<button id="addAfter2Ses" onclick="addAfter2Ses()">2秒后+1</button>
</div>
</div>
`;
}

function stateChanger(state,action){
if(state === undefined){
return 0;
}else{
console.log('add')
if(action.type === 'add'){
var newState = state + action.payload;
return newState;
// step2 根据操作生成新的 state 触发一个事件
}else{
return state;
}
}
}
//初始化 store
var store = Redux.createStore(stateChanger)

render(store);

//监听 store的变化 然后触发render
store.subscribe(()=>{
// step3 接受 事件重新 render
render(store)

})
</script>
</body>
</html>
  • 一个坑,就是异步更新 store的问题

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
// 新手经常犯的错误 ,把异步逻辑写在 
function asyncAdd2(){
store.dispatch({type:'add2',payload:2})
}


function stateChanger(state,action){
if(state === undefined){
return 0;
}else{
if(action.type === 'add'){
var newState = state + action.payload;
return newState;
// step2 根据操作生成新的 state 触发一个事件
}else if(action.type ==='add2'){
setTimeout(() => {
var newState = state + action.payload;
return newState;
}, 2000);

// 此时 setTimeout里函数的return是 箭头函数的返回值
/*
而这个
else if(action.type ==='add2'){
//没写 return
// 相当于 return undefined
}
*/
}else{
return state;
}
}
}

代码仓库