├── examples ├── Todo │ ├── src │ │ ├── components │ │ │ ├── Link.js │ │ │ ├── App.js │ │ │ ├── helper.js │ │ │ ├── TodoList.js │ │ │ └── Todo.js │ │ ├── index.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── visibilityFilter.js │ │ │ └── todos.js │ │ ├── store.js │ │ └── actions │ │ │ └── index.js │ └── todo.html ├── index.html └── webpack.config.js ├── test ├── index.js ├── connect.spec.js └── StoreProvider.spec.js ├── src ├── index.js ├── helper.js ├── connect.js └── StoreProvider.js ├── .gitignore ├── .travis.yml ├── .babelrc ├── .eslintrc ├── gulpfile.js ├── LICENSE ├── webpack.config.js ├── package.json └── README.md /examples/Todo/src/components/Link.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // @TODO: use jsdom to test -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as StoreProvider } from './StoreProvider.js' 2 | export { default as connect } from './connect.js' -------------------------------------------------------------------------------- /examples/Todo/src/index.js: -------------------------------------------------------------------------------- 1 | import App from './components/App'; 2 | 3 | const app = new App(); 4 | app.$inject(document.getElementById('app')); 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist 5 | lib 6 | es 7 | .nyc_output 8 | coverage 9 | coverage.lcov 10 | .vscode 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | script: 5 | - npm run lint 6 | - npm run test:cov 7 | after_success: 8 | - npm run coverage -------------------------------------------------------------------------------- /src/helper.js: -------------------------------------------------------------------------------- 1 | //interfaces 2 | export const IStateFetcher = { $$name: '$$isStateFetcher' }; 3 | export const IActionDispatcher = { $$name: '$$isActionDispatcher' }; 4 | 5 | IStateFetcher[IStateFetcher.$$name] = true; 6 | IActionDispatcher[IActionDispatcher.$$name] = true; 7 | -------------------------------------------------------------------------------- /examples/Todo/src/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 | 10 | export default todoApp -------------------------------------------------------------------------------- /examples/Todo/src/reducers/visibilityFilter.js: -------------------------------------------------------------------------------- 1 | const visibilityFilter = (state = 'SHOW_ALL', action) => { 2 | switch (action.type) { 3 | case 'SET_VISIBILITY_FILTER': 4 | return action.filter 5 | default: 6 | return state 7 | } 8 | } 9 | 10 | export default visibilityFilter -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["es2015"]], 3 | "env": { 4 | "test": { 5 | "plugins": ["istanbul"] 6 | }, 7 | "commonjs": { 8 | "presets": [ 9 | ["es2015", { "modules": false }] 10 | ] 11 | }, 12 | "es": { 13 | "presets": [] 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/Todo/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | import todoAppReducer from './reducers'; 3 | 4 | export function createTodoStore() { 5 | return createStore((state, action) => { 6 | console.log(action); 7 | return todoAppReducer(state, action); 8 | }, { 9 | todos: [] 10 | }); 11 | }; -------------------------------------------------------------------------------- /examples/Todo/src/components/App.js: -------------------------------------------------------------------------------- 1 | import Regular from 'regularjs'; 2 | import '../../../../src/StoreProvider'; 3 | import { createTodoStore } from '../store'; 4 | import './TodoList'; 5 | 6 | const App = Regular.extend({ 7 | name: 'App', 8 | template: ` 9 | 10 | 11 | 12 | `, 13 | config(data) { 14 | data.store = createTodoStore() 15 | } 16 | }); 17 | 18 | export default App; -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Examples - RegularRedux 6 | 13 | 14 | 15 |
16 |
RegularRedux 示例项目列表
17 | 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": [ 4 | "eslint:recommended", 5 | "plugin:import/recommended" 6 | ], 7 | "parserOptions": { 8 | "ecmaVersion": 6, 9 | "sourceType": "module", 10 | "ecmaFeatures": { 11 | "experimentalObjectRestSpread": true 12 | } 13 | }, 14 | "env": { 15 | "browser": true, 16 | "mocha": true, 17 | "node": true 18 | }, 19 | "rules": { 20 | "valid-jsdoc": 2, 21 | "no-console": 0, 22 | "no-unused-vars": 1 23 | }, 24 | "plugins": [ 25 | "import" 26 | ] 27 | } -------------------------------------------------------------------------------- /examples/Todo/src/actions/index.js: -------------------------------------------------------------------------------- 1 | let nextTodoId = 0 2 | export const addTodo = (text) => { 3 | return { 4 | type: 'ADD_TODO', 5 | id: nextTodoId++, 6 | text 7 | } 8 | } 9 | 10 | export const setVisibilityFilter = (filter) => { 11 | return { 12 | type: 'SET_VISIBILITY_FILTER', 13 | filter 14 | } 15 | } 16 | 17 | export const toggleTodo = (id) => { 18 | return { 19 | type: 'TOGGLE_TODO', 20 | id 21 | } 22 | } 23 | 24 | export const updateTodo = (id, text) => { 25 | return { 26 | type: 'UPDATE_TODO', 27 | id, 28 | text, 29 | } 30 | } -------------------------------------------------------------------------------- /examples/Todo/src/components/helper.js: -------------------------------------------------------------------------------- 1 | // helper functions goes here 2 | 3 | /** 4 | * filter visible todos 5 | * @param {Array} todos todos list array 6 | * @param {String} filter todo filter type 7 | */ 8 | export const getVisibleTodos = (todos, filter) => { 9 | switch (filter) { 10 | case 'SHOW_ALL': 11 | return todos 12 | case 'SHOW_COMPLETED': 13 | return todos.filter(t => t.completed) 14 | case 'SHOW_ACTIVE': 15 | return todos.filter(t => !t.completed) 16 | } 17 | }; 18 | 19 | 20 | export const ESCAPE_KEY = 27; 21 | export const ENTER_KEY = 13; -------------------------------------------------------------------------------- /examples/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin; 4 | 5 | var config = { 6 | output: { 7 | pathinfo: true, 8 | filename: "bundle.js", 9 | chunkFilename: '[name].js', 10 | }, 11 | module: { 12 | rules: [{ 13 | test: /\.js$/, 14 | exclude: /(node_modules|bower_components)/, 15 | loader: 'babel-loader', 16 | options: { 17 | cacheDirectory: true, 18 | presets: [ 19 | ["es2015", { "modules": false }] 20 | ], 21 | } 22 | }, { 23 | test: /\.html$/, 24 | loader: 'raw-loader' 25 | }] 26 | }, 27 | devtool: 'eval', 28 | }; 29 | 30 | module.exports = config; 31 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const gulp = require('gulp'); 4 | const webpack = require('webpack'); 5 | const webpackStream = require('webpack-stream'); 6 | const named = require('vinyl-named'); 7 | const plumber = require('gulp-plumber'); 8 | const all = require('gulp-all'); 9 | 10 | 11 | gulp.task('build:example', function () { 12 | const webpackConfig = require('./examples/webpack.config'); 13 | const examples = fs.readdirSync('./examples/').filter(file => fs.statSync(path.resolve(__dirname, `./examples/${file}`)).isDirectory()); 14 | return all(examples.map(example => { 15 | return gulp.src(`./examples/${example}/src/index.js`) 16 | .pipe(plumber()) 17 | .pipe(named()) 18 | .pipe(webpackStream(webpackConfig, webpack)) 19 | .pipe(gulp.dest(`./examples/${example}/build`)); 20 | })) 21 | }); 22 | -------------------------------------------------------------------------------- /test/connect.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import Regular from 'regularjs'; 3 | import { createStore } from 'redux'; 4 | import connect from '../src/connect'; 5 | import { IActionDispatcher, IStateFetcher } from '../src/helper'; 6 | 7 | describe('Regular', () => { 8 | describe('connect', () => { 9 | it('should implement IStateFetcher & mapState if provide mapState in options', () => { 10 | const Component = Regular.extend(); 11 | const TargetComponent = connect({ 12 | mapState(state) {} 13 | })(Component); 14 | const target = new TargetComponent(); 15 | expect(target).to.have.property(IStateFetcher.$$name); 16 | expect(target.mapState).to.be.a('function'); 17 | }); 18 | 19 | it('should implement IActionDispatcher if dispatch is true', () => { 20 | const Component = Regular.extend(); 21 | const TargetComponent = connect({ 22 | dispatch: true 23 | })(Component); 24 | const target = new TargetComponent(); 25 | expect(target).to.have.property(IActionDispatcher.$$name); 26 | }); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Capasky 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/connect.js: -------------------------------------------------------------------------------- 1 | import { IActionDispatcher, IStateFetcher } from './helper'; 2 | 3 | /** 4 | * connect a Regular component 5 | * @param {Object} def connect definition object 6 | * @param {Function} def.mapState map redux state to regular component's data, and will call $update method automatically 7 | * @param {Boolean} def.dispatch truly to inject dispatch to component 8 | * @returns {Function} return a function, you can use it to redefine (Component) to be reduxable 9 | */ 10 | export default function connect({ 11 | mapState, 12 | dispatch, 13 | }) { 14 | return function (originComponent) { 15 | if (mapState && typeof mapState === 'function') { 16 | originComponent = originComponent.implement(IStateFetcher).implement({ 17 | mapState(state) { 18 | const mappedData = mapState.call(this, state, this.data); 19 | mappedData && Object.assign(this.data, mappedData); 20 | return mappedData; 21 | } 22 | }); 23 | } 24 | if (dispatch) { 25 | originComponent = originComponent.implement(IActionDispatcher); 26 | } 27 | return originComponent; 28 | }; 29 | } 30 | -------------------------------------------------------------------------------- /examples/Todo/src/reducers/todos.js: -------------------------------------------------------------------------------- 1 | const todo = (state = {}, action) => { 2 | switch (action.type) { 3 | case 'ADD_TODO': 4 | return { 5 | id: action.id, 6 | text: action.text, 7 | completed: false 8 | }; 9 | case 'UPDATE_TODO': 10 | if (state.id !== action.id) { 11 | return state; 12 | } 13 | 14 | return Object.assign({}, state, { 15 | text: action.text 16 | }); 17 | case 'TOGGLE_TODO': 18 | if (state.id !== action.id) { 19 | return state; 20 | } 21 | 22 | return Object.assign({}, state, { 23 | completed: !state.completed 24 | }); 25 | 26 | default: 27 | return state; 28 | } 29 | } 30 | 31 | const todos = (state = [], action) => { 32 | switch (action.type) { 33 | case 'ADD_TODO': 34 | return [ 35 | ...state, 36 | todo(undefined, action) 37 | ]; 38 | case 'UPDATE_TODO': 39 | return state.map(t => 40 | todo(t, action) 41 | ); 42 | case 'TOGGLE_TODO': 43 | return state.map(t => 44 | todo(t, action) 45 | ); 46 | default: 47 | return state; 48 | } 49 | } 50 | 51 | export default todos -------------------------------------------------------------------------------- /examples/Todo/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TODO - Examples|RegularRedux 6 | 45 | 46 | 47 |
48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/Todo/src/components/TodoList.js: -------------------------------------------------------------------------------- 1 | import Regular from 'regularjs'; 2 | import './Todo'; 3 | import { toggleTodo, addTodo } from '../actions'; 4 | import { getVisibleTodos, ENTER_KEY, ESCAPE_KEY } from './helper'; 5 | import { connect } from '../../../../src'; 6 | 7 | const TodoList = Regular.extend({ 8 | name: 'TodoList', 9 | template: ` 10 | 18 | `, 19 | config(data) { 20 | Object.assign(data, { 21 | todos: [], 22 | text: '', 23 | }); 24 | }, 25 | onTodoClick(todo) { 26 | this.$dispatch(toggleTodo(todo.id)); 27 | }, 28 | onAddTodo() { 29 | const text = this.data.text.trim(); 30 | if (!text) { 31 | return; 32 | } 33 | this.$dispatch(addTodo(text)); 34 | this.data.text = ''; 35 | }, 36 | onKeyDown(ev) { 37 | if (ev.which === ENTER_KEY) { 38 | this.onAddTodo(); 39 | } else if (ev.which === ESCAPE_KEY) { 40 | this.data.text = ''; 41 | } 42 | } 43 | }); 44 | 45 | export default connect({ 46 | mapState(state) { 47 | return { 48 | todos: getVisibleTodos(state.todos, state.visibilityFilter) 49 | } 50 | }, 51 | dispatch: true, 52 | })(TodoList); 53 | -------------------------------------------------------------------------------- /src/StoreProvider.js: -------------------------------------------------------------------------------- 1 | import Regular from 'regularjs'; 2 | import { IActionDispatcher, IStateFetcher } from './helper'; 3 | 4 | /** 5 | * Container component to provide store 6 | * @component StoreProvider 7 | */ 8 | const StoreProvider = Regular.extend({ 9 | name: 'StoreProvider', 10 | template: '{#inc this.$body}', 11 | config(data) { 12 | if (!data.store) { 13 | throw Error('StoreProvider must provide store'); 14 | } 15 | }, 16 | modifyBodyComponent(component) { 17 | const store = this.data.store; 18 | // component can fetch data from store which 19 | // implement the DataFetcher interface 20 | if (component[IStateFetcher.$$name]) { 21 | if (typeof component.mapState !== 'function') { 22 | console.warn(`mapState method not found in component ${component.name}`); 23 | return; 24 | } 25 | 26 | const unsubscribe = store.subscribe(() => { 27 | if (component.$phase === 'destroyed') { 28 | return; 29 | } 30 | const state = store.getState(); 31 | const returnValue = component.mapState(state); 32 | if (returnValue !== false) { 33 | component.$update(); 34 | } 35 | }); 36 | 37 | component.$on('$destroy', unsubscribe) 38 | } 39 | // component can dispatch action to store which 40 | // implement the ActionDispatcher interface 41 | if (component[IActionDispatcher.$$name]) { 42 | component.$dispatch = store.dispatch.bind(store); 43 | } 44 | }, 45 | }); 46 | 47 | export default StoreProvider; 48 | -------------------------------------------------------------------------------- /examples/Todo/src/components/Todo.js: -------------------------------------------------------------------------------- 1 | import Regular from 'regularjs'; 2 | import { updateTodo } from '../actions'; 3 | import { ESCAPE_KEY, ENTER_KEY } from './helper'; 4 | import { connect } from '../../../../src'; 5 | 6 | const Todo = Regular.extend({ 7 | name: 'Todo', 8 | template: ` 9 |
10 | {#if mode === 'edit'} 11 | 12 | {#else} 13 | {todo.text} 14 | {/if} 15 |
`, 16 | config(data) { 17 | Object.assign(data, { 18 | mode: 'show', 19 | text: '' 20 | }) 21 | }, 22 | enterEdit() { 23 | if (this.data.todo.completed) { 24 | return; 25 | } 26 | this.data.mode = 'edit'; 27 | this.data.text = this.data.todo.text; 28 | }, 29 | cancel() { 30 | this.data.mode = 'show'; 31 | this.data.text = ''; 32 | }, 33 | onChange() { 34 | let { text, todo } = this.data; 35 | text = text.trim(); 36 | if (text && text !== todo.text) { 37 | //dispatch 38 | this.$dispatch(updateTodo(todo.id, text)); 39 | } 40 | }, 41 | onKeyDown(ev) { 42 | if (ev.which === ENTER_KEY) { 43 | this.onChange(); 44 | } else if (ev.which === ESCAPE_KEY) { 45 | this.cancel(); 46 | } 47 | } 48 | }); 49 | 50 | export default connect({ dispatch: true })(Todo); 51 | -------------------------------------------------------------------------------- /test/StoreProvider.spec.js: -------------------------------------------------------------------------------- 1 | import expect from 'expect.js' 2 | import Regular from 'regularjs'; 3 | import { createStore } from 'redux'; 4 | import connect from '../src/connect'; 5 | import { IActionDispatcher, IStateFetcher } from '../src/helper'; 6 | 7 | describe('Regular', () => { 8 | describe('StoreProvider', () => { 9 | const TodoList = Regular.extend({ 10 | name: 'TodoList', 11 | template: ` 12 | `, 17 | onTodoClick(todo) { 18 | this.$dispatch({ 19 | action: 'TOGGLE_TODO', 20 | payload: todo.id 21 | }); 22 | } 23 | }); 24 | 25 | const ConnectedTodoList = connect({ 26 | mapState(state) { 27 | return { 28 | todos: state.todos 29 | } 30 | }, 31 | dispatch: true 32 | })(TodoList); 33 | 34 | const App = Regular.extend({ 35 | template: ` 36 | 37 | 38 | 39 | `, 40 | config(data) { 41 | data.store = createStore((state, action) => { 42 | switch(action.type) { 43 | case 'TOGGLE_TODO': 44 | break; 45 | default: 46 | return state; 47 | } 48 | }, { todos: [] }); 49 | } 50 | }); 51 | 52 | it('should throw when store is not provided', () => { 53 | const Component = Regular.extend({ 54 | template: `` 55 | }); 56 | 57 | const fn = () => new Component(); 58 | expect(fn).to.throwError(); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var webpack = require('webpack') 4 | var env = process.env.NODE_ENV 5 | 6 | var regularExternal = { 7 | root: 'Regular', 8 | commonjs2: 'regularjs', 9 | commonjs: 'regularjs', 10 | amd: 'regularjs' 11 | } 12 | 13 | var reduxExternal = { 14 | root: 'Redux', 15 | commonjs2: 'redux', 16 | commonjs: 'redux', 17 | amd: 'redux' 18 | } 19 | 20 | var config = { 21 | externals: { 22 | 'regularjs': regularExternal, 23 | 'redux': reduxExternal 24 | }, 25 | module: { 26 | rules: [{ 27 | test: /\.js$/, 28 | loader: 'babel-loader', 29 | exclude: /node_modules/, 30 | options: { 31 | cacheDirectory: true, 32 | presets: [ 33 | ["es2015", { "modules": false }] 34 | ], 35 | plugins: ['transform-object-rest-spread'] 36 | } 37 | }, { 38 | test: /\.html$/, 39 | loader: 'raw-loader' 40 | }] 41 | }, 42 | output: { 43 | library: 'RegularRedux', 44 | libraryTarget: 'umd' 45 | }, 46 | plugins: [{ 47 | apply: function apply(compiler) { 48 | compiler.parser.plugin('expression global', function expressionGlobalPlugin() { 49 | this.state.module.addVariable('global', "(function() { return this; }()) || Function('return this')()") 50 | return false 51 | }) 52 | } 53 | }, 54 | new webpack.DefinePlugin({ 55 | 'process.env.NODE_ENV': JSON.stringify(env) 56 | }) 57 | ] 58 | } 59 | 60 | if (env === 'production') { 61 | config.plugins.push( 62 | new webpack.optimize.UglifyJsPlugin({ 63 | compressor: { 64 | pure_getters: true, 65 | unsafe: true, 66 | unsafe_comps: true, 67 | screw_ie8: true, 68 | warnings: false 69 | } 70 | }) 71 | ) 72 | } 73 | 74 | module.exports = config -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rgl-redux", 3 | "version": "0.0.2", 4 | "description": "Regularjs bindings for Redux", 5 | "main": "src/index.js", 6 | "module": "es/index.js", 7 | "scripts": { 8 | "example": "gulp build:example && cd examples && puer", 9 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 10 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", 11 | "build:umd": "cross-env BABEL_ENV=commonjs NODE_ENV=development webpack src/index.js dist/rgl-redux.js", 12 | "build:umd:min": "cross-env BABEL_ENV=commonjs NODE_ENV=production webpack src/index.js dist/rgl-redux.min.js", 13 | "build": "npm run build:commonjs && npm run build:es && npm run build:umd && npm run build:umd:min", 14 | "clean": "rimraf lib dist es coverage", 15 | "lint": "eslint src test", 16 | "prepublish": "npm run clean && npm run build", 17 | "test": "cross-env BABEL_ENV=commonjs NODE_ENV=test mocha --compilers js:babel-register --recursive --require ./test/index.js", 18 | "test:watch": "npm test -- --watch", 19 | "test:cov": "cross-env NODE_ENV=test nyc npm test", 20 | "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/regularjs/rgl-redux.git" 25 | }, 26 | "files": [ 27 | "dist", 28 | "lib", 29 | "src", 30 | "es" 31 | ], 32 | "keywords": [ 33 | "redux", 34 | "regularjs" 35 | ], 36 | "author": "regularjs", 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/regularjs/rgl-redux/issues" 40 | }, 41 | "homepage": "https://github.com/regularjs/rgl-redux", 42 | "peerDependencies": { 43 | "redux": "^3.6.0", 44 | "regularjs": "^0.6.0-beta.1" 45 | }, 46 | "devDependencies": { 47 | "babel-cli": "^6.24.0", 48 | "babel-core": "^6.23.1", 49 | "babel-eslint": "^7.2.1", 50 | "babel-loader": "^6.3.2", 51 | "babel-plugin-istanbul": "^4.1.1", 52 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 53 | "babel-preset-es2015": "^6.24.0", 54 | "babel-register": "^6.24.0", 55 | "codecov": "^3.7.1", 56 | "cross-env": "^3.2.4", 57 | "eslint": "^3.18.0", 58 | "eslint-plugin-import": "^2.2.0", 59 | "expect": "^1.20.2", 60 | "expect.js": "^0.3.1", 61 | "gulp": "^3.9.1", 62 | "gulp-all": "^1.1.0", 63 | "gulp-plumber": "^1.1.0", 64 | "istanbul": "^0.4.5", 65 | "mocha": "^3.2.0", 66 | "nyc": "^10.1.2", 67 | "puer": "leeluolee/puer#next", 68 | "raw-loader": "^0.5.1", 69 | "redux": "^3.6.0", 70 | "regularjs": "^0.6.0-beta.1", 71 | "rimraf": "^2.6.1", 72 | "vinyl-named": "^1.1.0", 73 | "webpack": "^2.2.1", 74 | "webpack-stream": "^3.2.0" 75 | }, 76 | "nyc": { 77 | "sourceMap": false, 78 | "instrument": false 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Regular Redux 2 | 一个微型模块用于 [Regular](http://regularjs.github.io) 组件实现[Redux](http://redux.js.org). 3 | 4 | [![build status](https://img.shields.io/travis/regularjs/rgl-redux/develop.svg?style=flat-square)](https://travis-ci.org/regularjs/rgl-redux) [![npm version](https://img.shields.io/npm/v/rgl-redux.svg?style=flat-square)](https://www.npmjs.com/package/rgl-redux) 5 | 6 | ```sh 7 | npm install rgl-redux 8 | ``` 9 | 10 | ## 运行要求 11 | * regularjs 0.6.0-beta.1以上版本 12 | * webpack+babel 用于构建基于ES6语法的应用 13 | 14 | ## 用法 15 | `module/App.js`: 16 | ```js 17 | import 'rgl-redux'; 18 | import './module/MyApp'; 19 | import { reducer } from './reducers' 20 | import { createStore } from 'redux'; 21 | 22 | const AppContainer = Regular.extend({ 23 | template: ` 24 | 25 | 26 | 27 | `, 28 | config(data) { 29 | data.store = createStore(reducer); 30 | }, 31 | }); 32 | ``` 33 | 34 | `components/MyComp.js`: 35 | ```js 36 | import { connect } from 'rgl-redux'; 37 | import { changeFoo } from './actions'; 38 | 39 | const MyComp = Regular.extend({ 40 | name: 'MyComp', 41 | template: '
{foo}
', 42 | onClick() { 43 | this.$dispatch(changeFoo('bar')); 44 | } 45 | }); 46 | 47 | export default connect({ 48 | mapState(state) { 49 | return { 50 | foo: state.foo, 51 | }; 52 | }, 53 | dispatch: true, 54 | })(MyComp); 55 | 56 | ``` 57 | ## 示例项目 58 | 示例项目位于 `examples` 目录,克隆项目后,运行 `npm run example` 59 | * [TodoApp 示例](examples/Todo) 60 | 61 | ## 文档 62 | ### StoreProvider 63 | 位于`src/StoreProvider.js` 64 | 65 | 一个Regular容器组件,StoreProvider组件作为提供redux store的容器组件,一般位于应用组件的最外层,需在该组件的 config 中初始化应用的store。StoreProvider类似 react-redux 的 Provider组件,提供了一个上下文环境,处于该环境内的Regular组件都可以通过[connect](#connect(definition))连接至store或得到进行action dispatch的能力。 66 | 67 | ## connect(definition) 68 | 位于`src/connect.js` 69 | 70 | 用于标识Regular组件,配合StoreProvider向组件内部动态注入mapState方法及 Redux store的dispatch方法。 71 | ### 参数 72 | * `definition.mapState(state, data)` _(Function)_: 该方法指定了如何从全局store的当前state中获取当前组件所需要的数据。指定该参数后,组件会订阅Redux store的更新,即在任何时刻store更新时,该方法会被调用。改方法返回一个对象,用于替换当前组件的data,组件的¥update方法会自动调用。如果需要阻止自动调用`$update`使得组件不进入脏检查阶段,则直接返回`false`即可。如: 73 | ```js 74 | connect({ 75 | mapState(state, data) { 76 | const foo = state.foo; 77 | if (!foo || foo.length === data.foo.length) { 78 | return false; 79 | } 80 | return { 81 | foo 82 | }; 83 | } 84 | }); 85 | ``` 86 | * `definition.dispatch` _(Boolean)_: 一个布尔属性指定当前组件是否需要进行分发特定操作(dispatch action)。该属性为任意`true`值时,位于`StoreProvider`组件内部的组件会被注入 `$dispatch` 方法,绑定自 Redux的 `store.dispatch` 方法。 87 | 88 | ## 路线图 89 | ### v0.2.0 90 | * broadcast event: StoreProvider内的广播事件支持 91 | * Complicated example: 更完整复杂的示例 92 | * ActionMap: 一种简洁的方式实现dispatch -> action的映射 93 | 94 | ### Future 95 | * timeline plugin: 时间线插件,用于提供实现`撤销`, `重做`的操作机制 96 | 97 | ## License 98 | MIT --------------------------------------------------------------------------------