36 | )
37 | }
38 | componentWillUnmount() {
39 | this.unsub();
40 | }
41 | }
--------------------------------------------------------------------------------
/myreact-redux/counter/src/components/Counter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from '../react-redux';
3 | import actions from '../store/actions/counter';
4 |
5 | class Counter extends Component {
6 | render() {
7 | return (
8 |
9 |
{`number: ${this.props.number}`}
10 |
11 |
12 |
13 | )
14 | }
15 | }
16 |
17 | const mapStateToProps = state => ({
18 | number: state.counter.number
19 | });
20 |
21 | export default connect(mapStateToProps, actions)(Counter);
22 |
--------------------------------------------------------------------------------
/myreact-redux/counter/src/components/Page.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from '../react-redux';
3 |
4 | class Page extends Component {
5 | render() {
6 | console.log(this.props.store);
7 | return (
8 |
Page
9 | )
10 | }
11 | }
12 |
13 | export default connect(()=>({}), ()=>({}))(Page);
--------------------------------------------------------------------------------
/myreact-redux/counter/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {Provider} from './react-redux';
4 | import Counter from './components/Counter';
5 | import store from './store';
6 |
7 |
8 |
9 | ReactDOM.render(
, document.getElementById('root'));
10 |
11 |
--------------------------------------------------------------------------------
/myreact-redux/counter/src/react-redux/components/Provider.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import storeShape from '../utils/storeShape';
3 |
4 | export default class Provider extends Component {
5 | static childContextTypes = {
6 | store: storeShape.isRequired
7 | }
8 |
9 | constructor(props) {
10 | super(props);
11 | this.store = props.store;
12 | }
13 |
14 | getChildContext() {
15 | return {
16 | store: this.store
17 | }
18 | }
19 |
20 | render() {
21 | /**
22 | * 早前返回的是 return Children.only(this.props.children)
23 | * 导致Provider只能包裹一个子组件,后来取消了此限制
24 | * 因此此处,我们直接返回 this.props.children
25 | */
26 | return this.props.children
27 | }
28 | }
--------------------------------------------------------------------------------
/myreact-redux/counter/src/react-redux/components/connect.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from 'redux';
3 | import storeShape from '../utils/storeShape';
4 | import shallowEqual from '../utils/shallowEqual';
5 | /**
6 | * mapStateToProps 默认不关联state
7 | * mapDispatchToProps 默认值为 dispatch => ({dispatch}),将 `store.dispatch` 方法作为属性传递给组件
8 | */
9 | const defaultMapStateToProps = state => ({});
10 | const defaultMapDispatchToProps = dispatch => ({ dispatch });
11 | function getDisplayName(WrappedComponent) {
12 | return WrappedComponent.displayName || WrappedComponent.name || 'Component';
13 | }
14 |
15 | export default function connect(mapStateToProps, mapDispatchToProps) {
16 | if(!mapStateToProps) {
17 | mapStateToProps = defaultMapStateToProps;
18 | }
19 | if (!mapDispatchToProps) {
20 | //当 mapDispatchToProps 为 null/undefined/false...时,使用默认值
21 | mapDispatchToProps = defaultMapDispatchToProps;
22 | }
23 | return function wrapWithConnect(WrappedComponent) {
24 | return class Connect extends Component {
25 | static contextTypes = {
26 | store: storeShape
27 | };
28 | static displayName = `Connect(${getDisplayName(WrappedComponent)})`;
29 |
30 | constructor(props, context) {
31 | super(props, context);
32 | this.store = context.store;
33 | //源码中是将 store.getState() 给了 this.state
34 | this.state = mapStateToProps(this.store.getState(), this.props);
35 | if (typeof mapDispatchToProps === 'function') {
36 | this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props);
37 | } else {
38 | //传递了一个 actionCreator 对象过来
39 | this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch);
40 | }
41 | }
42 | componentDidMount() {
43 | this.unsub = this.store.subscribe(() => {
44 | const mappedState = mapStateToProps(this.store.getState());
45 | if (shallowEqual(this.state, mappedState)) {
46 | return;
47 | }
48 | this.setState(mappedState);
49 | });
50 | }
51 | componentWillUnmount() {
52 | this.unsub();
53 | }
54 | render() {
55 | return (
56 |
57 | )
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/myreact-redux/counter/src/react-redux/index.js:
--------------------------------------------------------------------------------
1 | // Provider 通过context提供store
2 | // connect 连接仓库和组件,仓库的取值和订阅逻辑都放在在里面
3 | import connect from './components/connect';
4 | import Provider from './components/Provider';
5 |
6 | export {
7 | connect,
8 | Provider
9 | }
--------------------------------------------------------------------------------
/myreact-redux/counter/src/react-redux/utils/shallowEqual.js:
--------------------------------------------------------------------------------
1 | export default function shallowEqual(objA, objB) {
2 | if (objA === objB) {
3 | return true
4 | }
5 |
6 | const keysA = Object.keys(objA)
7 | const keysB = Object.keys(objB)
8 |
9 | if (keysA.length !== keysB.length) {
10 | return false
11 | }
12 |
13 | const hasOwn = Object.prototype.hasOwnProperty
14 | for (let i = 0; i < keysA.length; i++) {
15 | if (!hasOwn.call(objB, keysA[i]) ||
16 | objA[keysA[i]] !== objB[keysA[i]]) {
17 | return false
18 | }
19 | }
20 |
21 | return true
22 | }
23 |
--------------------------------------------------------------------------------
/myreact-redux/counter/src/react-redux/utils/storeShape.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default PropTypes.shape({
4 | subscribe: PropTypes.func.isRequired,
5 | dispatch: PropTypes.func.isRequired,
6 | getState: PropTypes.func.isRequired
7 | });
8 |
--------------------------------------------------------------------------------
/myreact-redux/counter/src/store/action-types.js:
--------------------------------------------------------------------------------
1 | export const INCREMENT = 'INCREMENT';
2 | export const DECREMENT = 'DECREMENT';
--------------------------------------------------------------------------------
/myreact-redux/counter/src/store/actions/counter.js:
--------------------------------------------------------------------------------
1 | import { INCREMENT, DECREMENT } from '../action-types';
2 |
3 | const counter = {
4 | add(number) {
5 | return {
6 | type: INCREMENT,
7 | number
8 | }
9 | },
10 | minus(number) {
11 | return {
12 | type: DECREMENT,
13 | number
14 | }
15 | }
16 | }
17 |
18 | export default counter;
--------------------------------------------------------------------------------
/myreact-redux/counter/src/store/index.js:
--------------------------------------------------------------------------------
1 | import reducer from './reducers';
2 | import reduxLogger from 'redux-logger';
3 | import { createStore, applyMiddleware } from '../redux';
4 | /**
5 | * state = {
6 | * counter: {
7 | * number: XXX
8 | * }
9 | * }
10 | */
11 |
12 | let store = createStore(reducer);
13 |
14 | export default applyMiddleware(reduxLogger)(createStore)(reducer);
--------------------------------------------------------------------------------
/myreact-redux/counter/src/store/reducers/counter.js:
--------------------------------------------------------------------------------
1 | import { INCREMENT, DECREMENT } from '../action-types';
2 |
3 | function counter(state = { number: 0 }, action) {
4 | switch (action.type) {
5 | case INCREMENT:
6 | return {
7 | ...state,
8 | number: state.number + action.number
9 | }
10 | case DECREMENT:
11 | return {
12 | ...state,
13 | number: state.number - action.number
14 | }
15 | default:
16 | return state;
17 | }
18 | }
19 |
20 | export default counter;
--------------------------------------------------------------------------------
/myreact-redux/counter/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import counter from './counter';
2 | import { combineReducers } from '../../redux';
3 |
4 | export default combineReducers({
5 | counter
6 | });
--------------------------------------------------------------------------------
/myreact-redux/todo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "counter",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.3.1",
7 | "react-dom": "^16.3.1",
8 | "react-scripts": "3.1.2"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": {
20 | "production": [
21 | ">0.2%",
22 | "not dead",
23 | "not op_mini all"
24 | ],
25 | "development": [
26 | "last 1 chrome version",
27 | "last 1 firefox version",
28 | "last 1 safari version"
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/todo/public/favicon.ico
--------------------------------------------------------------------------------
/myreact-redux/todo/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
29 |
React App
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/myreact-redux/todo/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/todo/public/logo192.png
--------------------------------------------------------------------------------
/myreact-redux/todo/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YvetteLau/Blog/67719f4c082d7dee3e1f99895e2e4c1c77e35fbe/myreact-redux/todo/public/logo512.png
--------------------------------------------------------------------------------
/myreact-redux/todo/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/myreact-redux/todo/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Footer from './Footer';
3 | import AddTodo from '../containers/AddTodo';
4 | import VisibleTodoList from '../containers/VisibleTodoList';
5 |
6 | const App = () => (
7 |
12 | )
13 |
14 | export default App;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FilterLink from '../containers/FilterLink';
3 |
4 | const Footer = () => (
5 |
6 | Show:
7 | {" "}
8 | All
9 | {', '}
10 | Active
11 | {', '}
12 | Completed
13 |
14 | )
15 |
16 |
17 | export default Footer;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/components/Link.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Link = ({ active, children, onClick }) => {
5 | if (active) {
6 | return
{children}
7 | }
8 | return (
9 |
{
12 | e.preventDefault();
13 | onClick();
14 | }}
15 | >
16 | {children}
17 |
18 |
19 | )
20 | }
21 |
22 | Link.propTypes = {
23 | active: PropTypes.bool.isRequired,
24 | children: PropTypes.node.isRequired,
25 | onClick: PropTypes.func.isRequired
26 | }
27 |
28 | export default Link
--------------------------------------------------------------------------------
/myreact-redux/todo/src/components/Todo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | const Todo = ({ onClick, completed, text }) => {
5 | return (
6 |
12 | {text}
13 |
14 | )
15 | }
16 |
17 | Todo.propTypes = {
18 | onClick: PropTypes.func.isRequired,
19 | completed: PropTypes.bool.isRequired,
20 | text: PropTypes.string.isRequired
21 | }
22 |
23 | export default Todo;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/components/TodoList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import Todo from './Todo';
4 |
5 | const TodoList = ({todos, onTodoClick}) => {
6 | return (
7 |
8 | {todos.map((todo, index) => (
9 | onTodoClick(index)} />
10 | ))}
11 |
12 | )
13 | }
14 |
15 | TodoList.propTypes = {
16 | todos: PropTypes.arrayOf(
17 | PropTypes.shape({
18 | completed: PropTypes.bool.isRequired,
19 | text: PropTypes.string.isRequired
20 | }).isRequired
21 | ).isRequired,
22 | onTodoClick: PropTypes.func.isRequired
23 | }
24 |
25 | export default TodoList;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/containers/AddTodo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from '../react-redux';
3 | import actions from '../store/actions';
4 |
5 | let AddTodo = ({ dispatch }) => {
6 | let input;
7 | return (
8 |
9 |
26 |
27 | )
28 | }
29 |
30 | AddTodo = connect()(AddTodo);
31 |
32 | export default AddTodo;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/containers/FilterLink.js:
--------------------------------------------------------------------------------
1 | import { connect } from '../react-redux';
2 | import actions from '../store/actions';
3 | import Link from '../components/Link';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | active: ownProps.filter === state.visibilityFilter
8 | }
9 | }
10 |
11 | const mapDispatchToProps = (dispatch, ownProps) => {
12 | return {
13 | onClick: () => {
14 | dispatch(actions.setVisibilityFilter(ownProps.filter));
15 | }
16 | }
17 | }
18 |
19 | const FilterLink = connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(Link);
23 |
24 | export default FilterLink;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/containers/VisibleTodoList.js:
--------------------------------------------------------------------------------
1 | import { connect } from '../react-redux';
2 | import TodoList from '../components/TodoList';
3 | import actions from '../store/actions';
4 |
5 | const getVisibleTodos = (todos, filter) => {
6 | switch (filter) {
7 | case 'SHOW_COMPLETED':
8 | return todos.filter(t => t.completed);
9 | case 'SHOW_ACTIVE':
10 | return todos.filter(t => !t.completed);
11 | case 'SHOW_ALL':
12 | default:
13 | return todos;
14 | }
15 | }
16 |
17 | const mapStateToProps = state => {
18 | return {
19 | todos: getVisibleTodos(state.todos, state.visibilityFilter)
20 | }
21 | }
22 |
23 | const mapDispatchToProps = dispatch => {
24 | return {
25 | onTodoClick: id => {
26 | dispatch(actions.toggleTodo(id))
27 | }
28 | }
29 | }
30 |
31 | const VisibleTodoList = connect(
32 | mapStateToProps,
33 | mapDispatchToProps
34 | )(TodoList);
35 |
36 | export default VisibleTodoList;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import {Provider} from './react-redux';
4 | import store from './store'
5 | import App from './components/App';
6 |
7 |
8 |
9 | ReactDOM.render(
, document.getElementById('root'));
10 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/react-redux/components/Provider.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import storeShape from '../utils/storeShape';
3 |
4 | export default class Provider extends Component {
5 | static childContextTypes = {
6 | store: storeShape.isRequired
7 | }
8 |
9 | constructor(props) {
10 | super(props);
11 | this.store = props.store;
12 | }
13 |
14 | getChildContext() {
15 | return {
16 | store: this.store
17 | }
18 | }
19 |
20 | render() {
21 | /**
22 | * 早前返回的是 return Children.only(this.props.children)
23 | * 导致Provider只能包裹一个子组件,后来取消了此限制
24 | * 因此此处,我们直接返回 this.props.children
25 | */
26 | return this.props.children
27 | }
28 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/src/react-redux/components/connect.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { bindActionCreators } from '../../redux';
3 | import storeShape from '../utils/storeShape';
4 | import shallowEqual from '../utils/shallowEqual';
5 | /**
6 | * mapStateToProps 默认不关联state
7 | * mapDispatchToProps 默认值为 dispatch => ({dispatch}),将 `store.dispatch` 方法作为属性传递给组件
8 | */
9 | const defaultMapStateToProps = state => ({});
10 | const defaultMapDispatchToProps = dispatch => ({ dispatch });
11 | function getDisplayName(WrappedComponent) {
12 | return WrappedComponent.displayName || WrappedComponent.name || 'Component';
13 | }
14 |
15 | export default function connect(mapStateToProps, mapDispatchToProps) {
16 | if(!mapStateToProps) {
17 | mapStateToProps = defaultMapStateToProps;
18 | }
19 | if (!mapDispatchToProps) {
20 | //当 mapDispatchToProps 为 null/undefined/false...时,使用默认值
21 | mapDispatchToProps = defaultMapDispatchToProps;
22 | }
23 | return function wrapWithConnect(WrappedComponent) {
24 | return class Connect extends Component {
25 | static contextTypes = {
26 | store: storeShape
27 | };
28 | static displayName = `Connect(${getDisplayName(WrappedComponent)})`;
29 |
30 | constructor(props, context) {
31 | super(props, context);
32 | this.store = context.store;
33 | //源码中是将 store.getState() 给了 this.state
34 | this.state = mapStateToProps(this.store.getState(), this.props);
35 | if (typeof mapDispatchToProps === 'function') {
36 | this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props);
37 | } else {
38 | //传递了一个 actionCreator 对象过来
39 | this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch);
40 | }
41 | }
42 | componentDidMount() {
43 | this.unsub = this.store.subscribe(() => {
44 | const mappedState = mapStateToProps(this.store.getState(), this.props);
45 | if (shallowEqual(this.state, mappedState)) {
46 | return;
47 | }
48 | this.setState(mappedState);
49 | });
50 | }
51 | componentWillUnmount() {
52 | this.unsub();
53 | }
54 | render() {
55 | return (
56 |
57 | )
58 | }
59 | }
60 | }
61 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/src/react-redux/index.js:
--------------------------------------------------------------------------------
1 | // Provider 通过context提供store
2 | // connect 连接仓库和组件,仓库的取值和订阅逻辑都放在在里面
3 | import connect from './components/connect';
4 | import Provider from './components/Provider';
5 |
6 | export {
7 | connect,
8 | Provider
9 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/src/react-redux/utils/shallowEqual.js:
--------------------------------------------------------------------------------
1 | export default function shallowEqual(objA, objB) {
2 | if (objA === objB) {
3 | return true
4 | }
5 |
6 | const keysA = Object.keys(objA)
7 | const keysB = Object.keys(objB)
8 |
9 | if (keysA.length !== keysB.length) {
10 | return false
11 | }
12 |
13 | const hasOwn = Object.prototype.hasOwnProperty
14 | for (let i = 0; i < keysA.length; i++) {
15 | if (!hasOwn.call(objB, keysA[i]) ||
16 | objA[keysA[i]] !== objB[keysA[i]]) {
17 | return false
18 | }
19 | }
20 |
21 | return true
22 | }
23 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/react-redux/utils/storeShape.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 |
3 | export default PropTypes.shape({
4 | subscribe: PropTypes.func.isRequired,
5 | dispatch: PropTypes.func.isRequired,
6 | getState: PropTypes.func.isRequired
7 | });
8 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/redux/applyMiddleware.js:
--------------------------------------------------------------------------------
1 | import compose from './compose';
2 |
3 | const applyMiddleware = (...middlewares) => createStore => (...args) => {
4 | let store = createStore(...args);
5 | let dispatch;
6 | const middlewareAPI = {
7 | getState: store.getstate,
8 | dispatch: (...args) => dispatch(...args)
9 | }
10 | let middles = middlewares.map(middleware => middleware(middlewareAPI));
11 | dispatch = compose(...middles)(store.dispatch);
12 | return {
13 | ...store,
14 | dispatch
15 | }
16 | }
17 |
18 | export default applyMiddleware;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/redux/bindActionCreators.js:
--------------------------------------------------------------------------------
1 | function bindActionCreator(actionCreator, dispatch) {
2 | return (...args) => dispatch(actionCreator(...args));
3 | }
4 | function bindActionCreators(actionCreator, dispatch) {
5 | //actionCreators 可以是一个普通函数或者是一个对象
6 | if (typeof actionCreator === 'function') {
7 | //如果是函数,返回一个函数,调用时,dispatch 这个函数的返回值
8 | bindActionCreator(actionCreator, dispatch);
9 | } else if (typeof actionCreator === 'object') {
10 | //如果是一个对象,那么对象的每一项都要都要返回 bindActionCreator
11 | const boundActionCreators = {}
12 | for (let key in actionCreator) {
13 | boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
14 | }
15 | return boundActionCreators;
16 | }
17 | }
18 |
19 | export default bindActionCreators;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/redux/combineReducers.js:
--------------------------------------------------------------------------------
1 | export default function combineReducers(reducers) {
2 | return function combination(state={}, action) {
3 | let nextState = {};
4 | let hasChanged = false; //状态是否改变
5 | for(let key in reducers) {
6 | const previousStateForKey = state[key];
7 | const nextStateForKey = reducers[key](previousStateForKey, action);
8 | nextState[key] = nextStateForKey;
9 | //只有所有的 nextStateForKey 均与 previousStateForKey 相等时,hasChanged 的值 false
10 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
11 | }
12 | //state 没有改变时,返回原对象
13 | return hasChanged ? nextState : state;
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/redux/compose.js:
--------------------------------------------------------------------------------
1 | export default function compose(...funcs) {
2 | //如果没有中间件
3 | if (funcs.length === 0) {
4 | return arg => arg
5 | }
6 | //中间件长度为1
7 | if (funcs.length === 1) {
8 | return funcs[0]
9 | }
10 |
11 | return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
12 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/src/redux/createStore.js:
--------------------------------------------------------------------------------
1 | export default function createStore(reducer) {
2 | let state;
3 | let listeners = [];
4 | const getState = () => state;
5 | const subscribe = (ln) => {
6 | listeners.push(ln);
7 | //订阅之后,也要允许取消订阅。不能只准订,不准退~
8 | const unsubscribe = () => {
9 | listeners = listeners.filter(listener => ln !== listener);
10 | }
11 | return unsubscribe;
12 | };
13 | const dispatch = (action) => {
14 | //reducer(state, action) 返回一个新状态
15 | state = reducer(state, action);
16 | listeners.forEach(ln => ln());
17 | }
18 | //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人
19 | dispatch({ type: `@@redux/__INIT__${Math.random()}` });
20 |
21 | return {
22 | getState,
23 | dispatch,
24 | subscribe
25 | }
26 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/src/redux/index.js:
--------------------------------------------------------------------------------
1 | import combineReducers from './combineReducers';
2 | import createStore from './createStore';
3 | import applyMiddleware from './applyMiddleware';
4 | import compose from './compose';
5 | import bindActionCreators from './bindActionCreators';
6 |
7 | export {
8 | combineReducers,
9 | createStore,
10 | applyMiddleware,
11 | bindActionCreators,
12 | compose
13 | }
14 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/action-types.js:
--------------------------------------------------------------------------------
1 | export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';
2 |
3 | export const ADD_TODO = 'ADD_TODO';
4 |
5 | export const TOGGLE_TODO = 'TOGGLE_TODO';
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/actions/index.js:
--------------------------------------------------------------------------------
1 | import todos from './todos';
2 | import visibilityFilter from './visibilityFilter';
3 |
4 | export default {
5 | ...todos,
6 | ...visibilityFilter
7 | }
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/actions/todos.js:
--------------------------------------------------------------------------------
1 | import * as types from '../action-types';
2 |
3 | const todos = {
4 | addTodo(text) {
5 | return {
6 | type: types.ADD_TODO,
7 | text
8 | }
9 | },
10 | toggleTodo(index) {
11 | return {
12 | type: types.TOGGLE_TODO,
13 | index
14 | }
15 | }
16 | }
17 |
18 | export default todos;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/actions/visibilityFilter.js:
--------------------------------------------------------------------------------
1 | import * as types from '../action-types';
2 |
3 | const setVisibilityFilter = {
4 | setVisibilityFilter(filter) {
5 | return {
6 | type: types.SET_VISIBILITY_FILTER,
7 | filter
8 | }
9 | }
10 | }
11 |
12 | export default setVisibilityFilter;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/index.js:
--------------------------------------------------------------------------------
1 | import {createStore} from '../redux';
2 | import todoApp from './reducers';
3 |
4 | const store = createStore(todoApp);
5 |
6 | export default store;
7 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import {combineReducers} from '../../redux';
2 | import todos from './todos';
3 | import visibilityFilter from './visibilityFilter'
4 |
5 | const todoApp = combineReducers({
6 | todos,
7 | visibilityFilter
8 | });
9 | export default todoApp;
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/reducers/todos.js:
--------------------------------------------------------------------------------
1 | import * as types from '../action-types';
2 |
3 | export default function todos(state = [], action) {
4 | switch (action.type) {
5 | case types.ADD_TODO:
6 | return [
7 | ...state,
8 | {
9 | text: action.text,
10 | completed: false
11 | }
12 | ]
13 | case types.TOGGLE_TODO:
14 | return state.map((todo, index) => {
15 | return action.index === index ?
16 | {
17 | ...todo,
18 | completed: !todo.completed
19 | } :
20 | todo
21 | })
22 | default:
23 | return state;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/myreact-redux/todo/src/store/reducers/visibilityFilter.js:
--------------------------------------------------------------------------------
1 | import * as types from '../action-types';
2 |
3 | const VisibilityFilters = {
4 | SHOW_ALL: 'SHOW_ALL'
5 | }
6 |
7 | export default function visibilityFilter(state = VisibilityFilters.SHOW_ALL, action) {
8 | switch (action.type) {
9 | case types.SET_VISIBILITY_FILTER:
10 | return action.filter;
11 | default:
12 | return state;
13 | }
14 | }
--------------------------------------------------------------------------------
/myredux/to-redux/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.9.0",
7 | "react-dom": "^16.9.0",
8 | "react-redux": "^7.1.0",
9 | "react-scripts": "3.1.1"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test",
15 | "eject": "react-scripts eject"
16 | },
17 | "eslintConfig": {
18 | "extends": "react-app"
19 | },
20 | "browserslist": {
21 | "production": [
22 | ">0.2%",
23 | "not dead",
24 | "not op_mini all"
25 | ],
26 | "development": [
27 | "last 1 chrome version",
28 | "last 1 firefox version",
29 | "last 1 safari version"
30 | ]
31 | },
32 | "devDependencies": {
33 | "redux": "^4.0.4"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/myredux/to-redux/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
React App
11 |
54 |
55 |
56 |
57 |
58 |
59 |
62 |
63 |
大家好,我是前端宇宙作者刘小夕
64 |
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/myredux/to-redux/src/index.js:
--------------------------------------------------------------------------------
1 | function createStore(reducer) {
2 | let state;
3 | let listeners = [];
4 | const getState = () => state;
5 | const subscribe = (ln) => {
6 | listeners.push(ln);
7 | //订阅之后,也要允许取消订阅。不能只准订,不准退~
8 | const unsubscribe = () => {
9 | listeners = listeners.filter(listener => ln !== listener);
10 | }
11 | return unsubscribe;
12 | };
13 | const dispatch = (action) => {
14 | //reducer(state, action) 返回一个新状态
15 | state = reducer(state, action);
16 | listeners.forEach(ln => ln());
17 | }
18 | //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人
19 | dispatch({ type: `@@redux/__INIT__${Math.random()}` });
20 |
21 | return {
22 | getState,
23 | dispatch,
24 | subscribe
25 | }
26 | }
27 |
28 | const initialState = {
29 | color: 'blue'
30 | }
31 |
32 | function reducer(state = initialState, action) {
33 | switch (action.type) {
34 | case 'CHANGE_COLOR':
35 | return {
36 | ...state,
37 | color: action.color
38 | }
39 | default:
40 | return state;
41 | }
42 | }
43 | const store = createStore(reducer);
44 |
45 | function renderApp(state) {
46 | console.log(state)
47 | renderHeader(state);
48 | renderContent(state);
49 | }
50 | function renderHeader(state) {
51 | const header = document.getElementById('header');
52 | header.style.color = state.color;
53 | }
54 | function renderContent(state) {
55 | const content = document.getElementById('content');
56 | content.style.color = state.color;
57 | }
58 |
59 | document.getElementById('to-blue').onclick = function () {
60 | store.dispatch({
61 | type: 'CHANGE_COLOR',
62 | color: 'rgb(0, 51, 254)'
63 | });
64 | // unsub();
65 | }
66 | document.getElementById('to-pink').onclick = function () {
67 | store.dispatch({
68 | type: 'CHANGE_COLOR',
69 | color: 'rgb(247, 109, 132)'
70 | });
71 | }
72 |
73 | renderApp(store.getState());
74 | var unsub = store.subscribe(() => renderApp(store.getState()));
--------------------------------------------------------------------------------
/myredux/to-redux/src/index1.js:
--------------------------------------------------------------------------------
1 | let state = {
2 | color: 'blue'
3 | }
4 | //渲染应用
5 | function renderApp() {
6 | renderHeader();
7 | renderContent();
8 | }
9 | //渲染 title 部分
10 | function renderHeader() {
11 | const header = document.getElementById('header');
12 | header.style.color = state.color;
13 | }
14 | //渲染内容
15 | function renderContent() {
16 | const content = document.getElementById('content');
17 | content.style.color = state.color;
18 | }
19 |
20 | renderApp();
21 |
22 | document.getElementById('to-blue').onclick = function () {
23 | state.color = 'rgb(0, 51, 254)';
24 | renderApp();
25 | }
26 | document.getElementById('to-pink').onclick = function () {
27 | state.color = 'rgb(247, 109, 132)';
28 | renderApp();
29 | }
--------------------------------------------------------------------------------
/myredux/to-redux/src/index2.js:
--------------------------------------------------------------------------------
1 | function createStore() {
2 | let state = {
3 | color: 'blue'
4 | }
5 | const getState = () => state;
6 | function changeState(action) {
7 | switch (action.type) {
8 | case 'CHANGE_COLOR':
9 | state = {
10 | ...state,
11 | color: action.color
12 | }
13 | return state;
14 | default:
15 | return state;
16 | }
17 | }
18 | return {
19 | getState,
20 | changeState
21 | }
22 | }
23 |
24 | function renderApp(state) {
25 | renderHeader(state);
26 | renderContent(state);
27 | }
28 | function renderHeader(state) {
29 | const header = document.getElementById('header');
30 | header.style.color = state.color;
31 | }
32 | function renderContent(state) {
33 | const content = document.getElementById('content');
34 | content.style.color = state.color;
35 | }
36 |
37 | document.getElementById('to-blue').onclick = function () {
38 | store.changeState({
39 | type: 'CHANGE_COLOR',
40 | color: 'rgb(0, 51, 254)'
41 | });
42 | renderApp(store.getState());
43 | }
44 | document.getElementById('to-pink').onclick = function () {
45 | store.changeState({
46 | type: 'CHANGE_COLOR',
47 | color: 'rgb(247, 109, 132)'
48 | });
49 | renderApp(store.getState());
50 | }
51 | const store = createStore();
52 | renderApp(store.getState());
--------------------------------------------------------------------------------
/myredux/to-redux2/README.md:
--------------------------------------------------------------------------------
1 | > state 的结构
2 |
3 | ```javascript
4 | let state = {
5 | theme: {
6 | color: 'blue'
7 | },
8 | counter: {
9 | number: 0
10 | }
11 | }
12 | ```
13 |
--------------------------------------------------------------------------------
/myredux/to-redux2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "redux-1",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.9.0",
7 | "react-dom": "^16.9.0",
8 | "react-redux": "^7.1.0",
9 | "react-scripts": "3.1.1"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test",
15 | "eject": "react-scripts eject"
16 | },
17 | "eslintConfig": {
18 | "extends": "react-app"
19 | },
20 | "browserslist": {
21 | "production": [
22 | ">0.2%",
23 | "not dead",
24 | "not op_mini all"
25 | ],
26 | "development": [
27 | "last 1 chrome version",
28 | "last 1 firefox version",
29 | "last 1 safari version"
30 | ]
31 | },
32 | "devDependencies": {
33 | "redux": "^4.0.4",
34 | "redux-logger": "^3.0.6"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/myredux/to-redux2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
React App
13 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/myredux/to-redux2/src/components/Counter.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import store from '../store';
3 | import actions from '../store/actions/counter';
4 |
5 | export default class Counter extends Component {
6 | constructor() {
7 | super();
8 | this.state = {
9 | number: store.getState().counter.number
10 | }
11 | }
12 | componentDidMount() {
13 | this.unsub = store.subscribe(()=>{
14 | this.setState({
15 | number: store.getState().counter.number
16 | })
17 | })
18 | }
19 | render() {
20 | return (
21 |
22 |
30 | {this.state.number}
31 |
39 |
40 | )
41 | }
42 | componentWillUnmount() {
43 | this.unsub();
44 | }
45 | }
--------------------------------------------------------------------------------
/myredux/to-redux2/src/components/Pannel.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import store from '../store';
3 | import actions from '../store/actions/theme';
4 | window.store = store;
5 |
6 | export default class Pannel extends Component {
7 | constructor() {
8 | super();
9 | this.state = {
10 | color: store.getState().theme
11 | }
12 | }
13 | componentDidMount() {
14 | this.unsub = store.subscribe(() => {
15 | this.setState({
16 | color: store.getState().theme
17 | });
18 | });
19 | }
20 | render() {
21 | return (
22 | <>
23 |
26 |
27 |
大家好,我是前端宇宙作者刘小夕
28 |
37 |
46 |
47 | >
48 | )
49 | }
50 | componentWillUnmount() {
51 | this.unsub();
52 | }
53 | }
--------------------------------------------------------------------------------
/myredux/to-redux2/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Counter from './components/Counter';
4 | import Pannel from './components/Pannel';
5 |
6 |
7 | ReactDOM.render(<>
>, document.getElementById('root'));
--------------------------------------------------------------------------------
/myredux/to-redux2/src/redux/applyMiddleware.js:
--------------------------------------------------------------------------------
1 | import compose from './compose';
2 |
3 | const applyMiddleware = (...middlewares) => createStore => (...args) => {
4 | let store = createStore(...args);
5 | let dispatch;
6 | const middlewareAPI = {
7 | getState: store.getstate,
8 | dispatch: (...args) => dispatch(...args)
9 | }
10 | let middles = middlewares.map(middleware => middleware(middlewareAPI));
11 | dispatch = compose(...middles)(store.dispatch);
12 | return {
13 | ...store,
14 | dispatch
15 | }
16 | }
17 |
18 | export default applyMiddleware;
--------------------------------------------------------------------------------
/myredux/to-redux2/src/redux/bindActionCreators.js:
--------------------------------------------------------------------------------
1 | function bindActionCreator(actionCreator, dispatch) {
2 | return (...args) => dispatch(actionCreator(...args));
3 | }
4 | function bindActionCreators(actionCreator, dispatch) {
5 | //actionCreators 可以是一个普通函数或者是一个对象
6 | if (typeof actionCreator === 'function') {
7 | //如果是函数,返回一个函数,调用时,dispatch 这个函数的返回值
8 | bindActionCreator(actionCreator, dispatch);
9 | } else if (typeof actionCreator === 'object') {
10 | //如果是一个对象,那么对象的每一项都要都要返回 bindActionCreator
11 | const boundActionCreators = {}
12 | for (let key in actionCreator) {
13 | boundActionCreators[key] = bindActionCreator(actionCreator[key], dispatch);
14 | }
15 | return boundActionCreators;
16 | }
17 | }
18 |
19 | export default bindActionCreators;
--------------------------------------------------------------------------------
/myredux/to-redux2/src/redux/combineReducers.js:
--------------------------------------------------------------------------------
1 | export default function combineReducers(reducers) {
2 | return function combination(state={}, action) {
3 | let nextState = {};
4 | let hasChanged = false; //状态是否改变
5 | for(let key in reducers) {
6 | const previousStateForKey = state[key];
7 | const nextStateForKey = reducers[key](previousStateForKey, action);
8 | nextState[key] = nextStateForKey;
9 | //只有所有的 nextStateForKey 均与 previousStateForKey 相等时,hasChanged 的值 false
10 | hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
11 | }
12 | //state 没有改变时,返回原对象
13 | return hasChanged ? nextState : state;
14 | }
15 | }
16 |
17 |
--------------------------------------------------------------------------------
/myredux/to-redux2/src/redux/compose.js:
--------------------------------------------------------------------------------
1 | export default function compose(...funcs) {
2 | //如果没有中间件
3 | if (funcs.length === 0) {
4 | return arg => arg
5 | }
6 | //中间件长度为1
7 | if (funcs.length === 1) {
8 | return funcs[0]
9 | }
10 |
11 | return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
12 | }
--------------------------------------------------------------------------------
/myredux/to-redux2/src/redux/createStore.js:
--------------------------------------------------------------------------------
1 | export default function createStore(reducer) {
2 | let state;
3 | let listeners = [];
4 | const getState = () => state;
5 | const subscribe = (ln) => {
6 | listeners.push(ln);
7 | //订阅之后,也要允许取消订阅。不能只准订,不准退~
8 | const unsubscribe = () => {
9 | listeners = listeners.filter(listener => ln !== listener);
10 | }
11 | return unsubscribe;
12 | };
13 | const dispatch = (action) => {
14 | //reducer(state, action) 返回一个新状态
15 | state = reducer(state, action);
16 | listeners.forEach(ln => ln());
17 | }
18 | //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人
19 | dispatch({ type: `@@redux/__INIT__${Math.random()}` });
20 |
21 | return {
22 | getState,
23 | dispatch,
24 | subscribe
25 | }
26 | }
--------------------------------------------------------------------------------
/myredux/to-redux2/src/redux/index.js:
--------------------------------------------------------------------------------
1 | import combineReducers from './combineReducers';
2 | import createStore from './createStore';
3 | import applyMiddleware from './applyMiddleware';
4 | import compose from './compose';
5 | import bindActionCreators from './bindActionCreators';
6 |
7 | export {
8 | combineReducers,
9 | createStore,
10 | applyMiddleware,
11 | bindActionCreators,
12 | compose
13 | }
14 |
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/action-types.js:
--------------------------------------------------------------------------------
1 | export const INCRENENT = 'INCRENENT';
2 | export const DECREMENT = 'DECREMENT';
3 |
4 | export const CHANGE_COLOR = 'CHANGE_COLOR';
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/actions/counter.js:
--------------------------------------------------------------------------------
1 | import { INCRENENT, DECREMENT } from '../action-types';
2 |
3 | const counter = {
4 | add(number) {
5 | return {
6 | type: INCRENENT,
7 | number
8 | }
9 | },
10 | minus(number) {
11 | return {
12 | type: DECREMENT,
13 | number
14 | }
15 | }
16 | }
17 |
18 | export default counter;
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/actions/theme.js:
--------------------------------------------------------------------------------
1 | import { CHANGE_COLOR } from '../action-types';
2 |
3 | let theme = {
4 | changeColor(color) {
5 | return {
6 | type: CHANGE_COLOR,
7 | color
8 | }
9 | }
10 | }
11 |
12 | export default theme;
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from '../redux';
2 | import reduxLogger from 'redux-logger';
3 | import reducer from './reducers';
4 |
5 | export default applyMiddleware(reduxLogger)(createStore)(reducer);
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/reducers/counter.js:
--------------------------------------------------------------------------------
1 | import { INCRENENT, DECREMENT } from '../action-types';
2 |
3 | export default function counter(state={number: 0}, action) {
4 | switch (action.type) {
5 | case INCRENENT:
6 | return {
7 | ...state,
8 | number: state.number + action.number
9 | }
10 | case DECREMENT:
11 | return {
12 | ...state,
13 | number: state.number - action.number
14 | }
15 | default:
16 | return state;
17 | }
18 | }
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/reducers/index.js:
--------------------------------------------------------------------------------
1 | import counter from './counter';
2 | import theme from './theme';
3 | import {combineReducers} from '../../redux';
4 |
5 | export default combineReducers({
6 | counter,
7 | theme
8 | });;
--------------------------------------------------------------------------------
/myredux/to-redux2/src/store/reducers/theme.js:
--------------------------------------------------------------------------------
1 | import { CHANGE_COLOR } from '../action-types';
2 |
3 | export default function theme(state = {color: 'blue'}, action) {
4 | switch (action.type) {
5 | case CHANGE_COLOR:
6 | return {
7 | ...state,
8 | color: action.color
9 | }
10 | default:
11 | return state;
12 | }
13 | }
--------------------------------------------------------------------------------