浅谈对redux/react-redux的理解以及手动实现redux/react-redux
什么是redux?为什么我们要用redux?
引用官方的原话:Redux is a predictable state container for JavaScript apps.,用大白话说redux就一个容器,这个容器为我们提供了一种管理公共状态的方案,方便不同组件之间的状态共享。摆脱了层层透传数据的繁琐,利于项目数据的维护
怎么自己实现redux
既然redux是管理公共状态的方案,那么组件肯定有使用甚至更新这些状态的需求,想要实现这些需求需要通过:获取状态(getState),改变状态(dispatch),通知组件响应状态改变(subscribe)这三个方法,那么我们需要创建一个store.js文件,用于统一管理,
1. store.js
export function createStore(reducer){
let State = { count:0 }
let observers = []
// 获取当前的state数据
const getState = function() {
return State
}
// 派发action改变state
const dispatch = function(action) {
State = reducer(State, action)
observers.forEach(fn => fn())
}
// 监听state的改变,通知组件,组件响应视图更新
function subscribe(fn) {
observers.push(fn)
}
return { getState, subscribe, dispatch }
}
2.reducer.js
export function reducer(state,action){
switch(action.type){
case 'add':
return {
...state,
count:state.count+1
}
default:
return state;
}
}
这个时候你可以在index.js文件中尝试看看这个store成功没
3.index.js
import { createStore } from './store'
import { reducer } from './reducer'
const store = createStore(reducer);
console.log('before----',store.getState())
store.subscribe(() => { console.log('组件收到改变了') })
store.dispatch({type:'add'})
console.log('after----',store.getState())
结果如下:
以上就是一个简单的redux实现方案,当然我相信仅仅这样并不能满足大家的需求,因为这样虽然实现了状态共享但是我们再实际运用中。每个组件都需要引入store,然后在去调用它的getState,dispatch,subscribe,这样显得代码比较冗杂,我们希望的是通过一些巧妙的设计和方法,不用每一个组件都引入store就可以达到共享里状态的目的,这个时候,react-redux就登场啦
什么是react-redux?
我理解的react-redux就是连接组件和store的一个工具,其中他提供的方法Provider,connect两个API,使我们不用每一个组件都引入store就可以共享store里面的状态,原理大致就是:Provider包裹根组件,将store放进this.context里,connect将store里面的state、dispatch合并进了this.props,并自动订阅更新,下面我们来看一下如何实现这两个API:
怎么自己实现react-redux
1.Provider
provider的实现依赖context这个API,他有一些固定写法,如果不太懂可以自行查阅,这里就不赘述。
import React,{Component} from 'react'
import PropTypes from 'prop-types'
export class Provider extends Component {
// childContextTypes来指定context对象的属性,固定写法
static childContextTypes = {
store: PropTypes.object
}
// getChildContext方法,返回context对象,固定写法
getChildContext() {
return { store: this.props.store }
}
// 渲染被provider包裹的组件
render() {
return this.props.children
}
}
完成Provider后,我们就能在组件中通过this.context.store这样的形式取到store,不需要再单独import store。
2.connect
connect是一个方法,两个参数mapStateToProps,mapDispatchToProps,然后返回一个高阶组件,这个高阶组件接受一个组件Com作为参数,然后返回一个新的组件,这个高阶组件的目的就是通过mapStateToProps,mapDispatchToProps给你传入的组件Com添加一些props,相当于把state和dispatch映射到了Com组件的props上,在组件里面就乐意通过this.props获取这些state和dispatch
import React,{Component} from 'react'
import PropTypes from 'prop-types'
export function connect(mapStateToProps,mapDispatchToProps){
return function(Com){
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
componentDidMount() {
const { store:{ subscribe } } = this.context;
subscribe(this.handleChangeView);
}
handleChangeView = () => {
// 用你想要的方法去更新视图,这里我就要setState方法
this.setState({})
}
render(){
const { store:{ getState,dispatch },props } = this.context;
return(
<Com
{...mapStateToProps(getState())}
{...mapDispatchToProps(dispatch)}
/>
)
}
}
return Connect;
}
}
完成connect后,只需要在组件中用connec包裹我们的组件,我们就能在组件中通过this.props这样的形式取store里面的信息,并且视图跟着响应。
最后用一个demo来测试一下这个react-redux,我是自己从零手动搭建的,如果觉得麻烦,可以用react的脚手架创建。
项目目录如下:
关键代码:
// package.json
{
"name": "react-redux",
"version": "1.0.0",
"author": "ymx",
"description": "react-redux原理理解",
"keywords": [
"react-redux"
],
"scripts": {
"start": "webpack-dev-server --config webpack.config.js"
},
"dependencies": {
"@babel/core": "^7.13.8",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/preset-env": "^7.13.9",
"@babel/preset-react": "^7.12.13",
"babel-loader": "^8.2.2",
"html-webpack-plugin": "^5.2.0",
"prop-types": "^15.7.2",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"webpack": "^5.24.3",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.2"
}
}
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
plugins: [
new HtmlWebpackPlugin({template: './index.html'})
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react'],
plugins: ['@babel/plugin-proposal-class-properties']
}
}
},
]}
}
// store.js
export function createStore(reducer){
let State = { count:0 }
let observers = []
const getState = function() {
return State
}
const dispatch = function(action) {
State = reducer(State, action)
observers.forEach(fn => fn())
}
function subscribe(fn) {
observers.push(fn)
}
return { getState, subscribe, dispatch }
}
// reducer.js
export function reducer(state,action){
switch(action.type){
case 'add':
return {
...state,
count:state.count+1
}
default:
return state;
}
}
// react-redux.js
import React,{Component} from 'react'
import PropTypes from 'prop-types'
// Provider
export class Provider extends Component {
static childContextTypes = {
store: PropTypes.object
}
getChildContext() {
return { store: this.props.store }
}
render() {
return this.props.children
}
}
// connect
export function connect(mapStateToProps,mapDispatchToProps){
return function(Com){
class Connect extends Component{
static contextTypes = {
store: PropTypes.object
}
componentDidMount() {
const { store:{ subscribe } } = this.context;
subscribe(this.handleChangeView);
}
handleChangeView = () => {
// 用你想要得方法去更新视图,这里我就要setState方法
this.setState({})
}
render(){
const { store:{ getState,dispatch },props } = this.context;
return(
<Com
{...mapStateToProps(getState())}
{...mapDispatchToProps(dispatch)}
/>
)
}
}
return Connect;
}
}
// App.jsx
import React,{ Component } from 'react';
import { connect } from './react-redux'
import PropTypes from 'prop-types';
class App extends Component{
static contextTypes = {
store: PropTypes.object
}
Add = () => {
this.props.addCount()
}
render(){
const { count } = this.props;
return <h2 style={{ cursor:'pointer' }} onClick={this.Add}>AppCount---{count}</h2>
}
}
const mapStateToProps = state => {
return {
count: state.count
}
}
const mapDispatchToProps = dispatch => {
return {
addCount: () => {
dispatch({type:'add'})
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(App);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from './react-redux';
import { createStore } from './store'
import { reducer } from './reducer'
import App from './App.jsx'
const store = createStore(reducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
源码地址:react-redux
参考文章: