Skip to content

Commit

Permalink
react-redux-source
Browse files Browse the repository at this point in the history
  • Loading branch information
YvetteLau committed Oct 4, 2019
1 parent efce8ab commit f6f77a1
Show file tree
Hide file tree
Showing 26 changed files with 444 additions and 0 deletions.
37 changes: 37 additions & 0 deletions myreact-redux/counter/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "counter",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.10.1",
"react-dom": "^16.10.1",
"react-redux": "^7.1.1",
"react-scripts": "3.1.2",
"redux": "^4.0.4",
"redux-logger": "^3.0.6",
"redux-persist": "^6.0.0",
"redux-promise": "^0.6.0",
"redux-thunk": "^2.3.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file added myreact-redux/counter/public/favicon.ico
Binary file not shown.
35 changes: 35 additions & 0 deletions myreact-redux/counter/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<style>
button{
height: 40px;
width: 80px;
background: pink;
text-align: center;
outline: none;
border: 0;
margin-right: 10px;
}
</style>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Binary file added myreact-redux/counter/public/logo192.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added myreact-redux/counter/public/logo512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions myreact-redux/counter/public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
2 changes: 2 additions & 0 deletions myreact-redux/counter/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
41 changes: 41 additions & 0 deletions myreact-redux/counter/src/components/Counter-no-react-redux.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import store from '../store';
import actions from '../store/actions/counter';
/**
* reducer 是 combineReducer({counter, ...})
* state 的结构为
* {
* counter: {number: 0},
* ....
* }
*/
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
number: store.getState().counter.number
}
}
componentDidMount() {
this.unsub = store.subscribe(() => {
if(this.state.number === store.getState().counter.number) {
return;
}
this.setState({
number: store.getState().counter.number
});
});
}
render() {
return (
<div>
<p>{`number: ${this.state.number}`}</p>
<button onClick={() => {store.dispatch(actions.add(2))}}>+</button>
<button onClick={() => {store.dispatch(actions.minus(2))}}>-</button>
<div>
)
}
componentWillUnmount() {
this.unsub();
}
}
21 changes: 21 additions & 0 deletions myreact-redux/counter/src/components/Counter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { Component } from 'react';
import { connect } from '../react-redux';
import actions from '../store/actions/counter';

class Counter extends Component {
render() {
return (
<div>
<p>{`number: ${this.props.number}`}</p>
<button onClick={() => { this.props.add(2) }}>+</button>
<button onClick={() => { this.props.minus(2) }}>-</button>
</div>
)
}
}

const mapStateToProps = state => ({
number: state.counter.number
});

export default connect(mapStateToProps, actions)(Counter);
13 changes: 13 additions & 0 deletions myreact-redux/counter/src/components/Page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React, { Component } from 'react';
import { connect } from '../react-redux';

class Page extends Component {
render() {
console.log(this.props.store);
return (
<div>Page</div>
)
}
}

export default connect(()=>({}), ()=>({}))(Page);
10 changes: 10 additions & 0 deletions myreact-redux/counter/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from './react-redux';
import Counter from './components/Counter';
import store from './store';



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

28 changes: 28 additions & 0 deletions myreact-redux/counter/src/react-redux/components/Provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { Component } from 'react';
import storeShape from '../utils/storeShape';

export default class Provider extends Component {
static childContextTypes = {
store: storeShape.isRequired
}

constructor(props) {
super(props);
this.store = props.store;
}

getChildContext() {
return {
store: this.store
}
}

render() {
/**
* 早前返回的是 return Children.only(this.props.children)
* 导致Provider只能包裹一个子组件,后来取消了此限制
* 因此此处,我们直接返回 this.props.children
*/
return this.props.children
}
}
61 changes: 61 additions & 0 deletions myreact-redux/counter/src/react-redux/components/connect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import storeShape from '../utils/storeShape';
import shallowEqual from '../utils/shallowEqual';
/**
* mapStateToProps 默认不关联state
* mapDispatchToProps 默认值为 dispatch => ({dispatch}),将 `store.dispatch` 方法作为属性传递给组件
*/
const defaultMapStateToProps = state => ({});
const defaultMapDispatchToProps = dispatch => ({ dispatch });
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}

export default function connect(mapStateToProps, mapDispatchToProps) {
if(!mapStateToProps) {
mapStateToProps = defaultMapStateToProps;
}
if (!mapDispatchToProps) {
//当 mapDispatchToProps 为 null/undefined/false...时,使用默认值
mapDispatchToProps = defaultMapDispatchToProps;
}
return function wrapWithConnect(WrappedComponent) {
return class Connect extends Component {
static contextTypes = {
store: storeShape
};
static displayName = `Connect(${getDisplayName(WrappedComponent)})`;

constructor(props, context) {
super(props, context);
this.store = context.store;
//源码中是将 store.getState() 给了 this.state
this.state = mapStateToProps(this.store.getState(), this.props);
if (typeof mapDispatchToProps === 'function') {
this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props);
} else {
//传递了一个 actionCreator 对象过来
this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch);
}
}
componentDidMount() {
this.unsub = this.store.subscribe(() => {
const mappedState = mapStateToProps(this.store.getState());
if (shallowEqual(this.state, mappedState)) {
return;
}
this.setState(mappedState);
});
}
componentWillUnmount() {
this.unsub();
}
render() {
return (
<WrappedComponent {...this.props} {...this.state} {...this.mappedDispatch} />
)
}
}
}
}
9 changes: 9 additions & 0 deletions myreact-redux/counter/src/react-redux/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Provider 通过context提供store
// connect 连接仓库和组件,仓库的取值和订阅逻辑都放在在里面
import connect from './components/connect';
import Provider from './components/Provider';

export {
connect,
Provider
}
22 changes: 22 additions & 0 deletions myreact-redux/counter/src/react-redux/utils/shallowEqual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export default function shallowEqual(objA, objB) {
if (objA === objB) {
return true
}

const keysA = Object.keys(objA)
const keysB = Object.keys(objB)

if (keysA.length !== keysB.length) {
return false
}

const hasOwn = Object.prototype.hasOwnProperty
for (let i = 0; i < keysA.length; i++) {
if (!hasOwn.call(objB, keysA[i]) ||
objA[keysA[i]] !== objB[keysA[i]]) {
return false
}
}

return true
}
7 changes: 7 additions & 0 deletions myreact-redux/counter/src/react-redux/utils/storeShape.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import PropTypes from 'prop-types'

export default PropTypes.shape({
subscribe: PropTypes.func.isRequired,
dispatch: PropTypes.func.isRequired,
getState: PropTypes.func.isRequired
});
16 changes: 16 additions & 0 deletions myreact-redux/counter/src/redux/applyMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const applyMiddleware = (...middlewares) => createStore => (...args) => {
let store = createStore(...args);
let dispatch;
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
let middles = middlewares.map(middle => middle(middlewareAPI));
dispatch = middles.reduceRight((prev, current) => current(prev), store.dispatch);
return {
...store,
dispatch
}
}

export default applyMiddleware;
14 changes: 14 additions & 0 deletions myreact-redux/counter/src/redux/combineReducers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default function combineReducers(reducers) {
//返回 reducer
return function combination(state = {}, action) {
let hasChanged = false;
let nextState = {};
for(let key in reducers) {
const previousStateForKey = state[key];
const nextStateForKey = reducers[key](previousStateForKey, action);
nextState[key] = nextStateForKey;
hasChanged = hasChanged || previousStateForKey !== nextStateForKey;
}
return hasChanged ? nextState : state;
}
}
9 changes: 9 additions & 0 deletions myreact-redux/counter/src/redux/compose.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function compose(...fns) {
if(fns.length === 0) {
return args => args
}
if(fns.length === 1) {
return fn[0];
}
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
25 changes: 25 additions & 0 deletions myreact-redux/counter/src/redux/createStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export default function createStore(reducer, preloadState, enhancer) {
let state;
let listeners = [];

const dispatch = action => {
state = reducer(state, action);
listeners.forEach(ln => ln());
}
const getState = () => {
return state
};
const subscribe = listen => {
listeners.push(listen);
return function unsubscribe() {
listeners = listeners.filter(ln => ln !== listen);
return listeners;
}
}
dispatch({type: '@@REDUX/__INIT__'});
return {
getState,
dispatch,
subscribe
}
}
Loading

0 comments on commit f6f77a1

Please sign in to comment.