CY

使生如夏花之绚烂,死如秋叶之静美

  1. 1. 前言
  2. 2. 创建模板
  3. 3. 创建 Store
  4. 4. 创建 Provider
  5. 5. 创建链接组件 Connect
  6. 6. 试试链接起来!
  7. 7. 订阅更新

前言

在 React 组件中,大部分组件或多或少都会需要一下 状态, 来维持 & 切换自身的 UI 状态, 他可能是自身的 State, 也许是外部传递的 Props, 都可以作为自身 UI 的一个切换的控制开关。

整个数据流都是从上至下的单项数据流(State -> Props),

3345073650-5a178e6f9acb2_articlex.gif

当层级跨越过深,又或者兄弟层级的时候状态传递会很复杂, 组件之间难以“互动”, 你可以将状态替身到一个上层 Container 进行管理然后分发给下面的组件(状态提升), 组件使用回调来改变上层数据, 当嵌套过多状态过多, 就不好办咯 ~。

1662100370-5a178e850b597_articlex.gif

为了更好的管理状态,Redux 将所有状态放置于顶层,所有的 state 都以一个对象树的形式储存在一个单一的 store 中,分发给各个组件。

1108238647-5a178e9523864_articlex.gif

Redux 推崇 Immutability,接受 action,reducer 接受数据,返回一个全新的 State(Action -> Reducer -> New State),…太繁琐了,算了,进正题 参照其实现一个简单的状态管理

redux.png

创建模板

在此之前先创建 React 基础模版,为了方便使用的是 CRA!

1
2
3
4
npx create-react-app app
cd app
mkdir lib/mini-redux # 将在这里存放
npm start

最终 API,简化 Redux API,No Reducer

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
const state = {
counter: 0
}

const actions = {
increment(state, data) {
state.counter += data
},
decrement(state, data) {
state.counter -= data
}
}

const Component = props => (
<>
<h1>{props.counter}</h1>
<button onClick={props.increment}>+</button>
<button onClick={props.decrement}>-</button>
</>
)

connect({
state: state => ({ conter: state.counter }),
actions: acions => ({
increment: actions.increment,
decrement: actions.decrement
})
})(Component)

创建 Store

Redux:单一数据源,全局 store,immutable。 是单一对象数,我们创建一个存放数据/状态(Store)的对象,当然我们不考虑其他的,不同 React-Redux 接受更多参数,他只接受一个 State 和 actions。

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
export default class Store {
constructor(state = {}, actions = {}) {
this.state = this.state
this.actions = this.rewriteActions(state, actions)
this.listeners = []
}

// 最终 action 是以 xxAction(data) 传入组建使用,所以这里改造一下。
rewriteActions(state = {}, actions = {}) {
Object.keys(actions).forEach(key => {
const fn = actions[key]
actions[key] = function(data) {
fn(state, data) // 这里可以使用 immer 做不可变
this.listeners.forEach(listener => listener()) // 当数据更改后执行监听
}.bind(this)
})
}

subscribe(listener) {
this.listeners.push(listener)
}

unSubscribe(listener) {
this.listeners = this.listeners.filter(f => f !== listener)
}
}

创建 Provider

react-redux 提供了两个重要的对象,Provider 和 connect,前者使 React 组件可被连接(connectable),后者把 React 组件和 Redux 的 store 真正连接起来。

上面差不多就做好了一个简易的 Store,然后我们创建 Store Provider 以提供给 React 各个组件,也就是顶层的 Provider。

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';

//创建 Context
const Context = React.createContext(null);

export class Provider extends React.Component {
render() {
return (
<Context.Provider value={this.props.store;}>{this.props.children}</Context.Provider>
);
}
}

创建链接组件 Connect

其实就是一个 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
export const connect = (
_state = () => ({}),
_actions = () => ({})
) => Component =>
class extends React.Component {
static contextType = Context
constructor(props) {
super(props)
this.state = {
store: {}
}
}
componentDidMount() {
const { state, actions } = this.context

this.setState({
store: {
..._state(state),
..._actions(actions)
}
})
}
render() {
return <Component {...this.state.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
34
35
36
37
38
39
40
41
42
43
import Store from './mini-redux/store'
import { connect, Provider } from './mini-redux/connect'

const state = {
counter: 0
}

const actions = {
increment(state, data) {
state.counter += data
},
decrement(state, data) {
state.counter -= data
}
}

const store = new Store({ state, actions })

@connect({
state: state => ({ conter: state.counter }),
actions: acions => ({
increment: actions.increment,
decrement: actions.decrement
})
})
class App extends React.Component {
render() {
return (
<div className="App">
<p>{this.props.conter}</p>
<button onClick={() => this.props.increment(1)}>+</button>
<button onClick={() => this.props.decrement(1)}>-</button>
</div>
)
}
}

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

点击 + 号,可以看到我们的 Store 值确实是改变了但是为什么页面没有更新呢?

:(.jpg

订阅更新

因为 react 更新需要手动调用 setState,所以我们添加订阅,当调用 actions 更新状态时我们对 connect 进行更新(setState)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 对 connect 进行改造
componentDidMount() {
this.subscribe();
this.context.subscribe(() => this.subscribe()) // 将组建添加到更新队列
}

componentWillMount(){
this.context.unSubscribe(()=>this.subscribe) //取消监听
}

subscribe() {
const { state, actions } = this.context

this.setState({
store: {
..._state(state),
..._actions(actions)
}
})

大功告成!至此就实现了一个极其建议的 redux。

:).png

ref:

本文作者 : CY
本文使用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 协议
本文链接 : https://runtua.cn/2019/03/12/redux-to-simple/

本文最后更新于 天前,文中所描述的信息可能已发生改变