├── examples ├── todo │ ├── src │ │ ├── webapi.ts │ │ ├── actor │ │ │ ├── filter-actor.ts │ │ │ ├── text-actor.ts │ │ │ └── todo-actor.ts │ │ ├── index.tsx │ │ ├── ql.ts │ │ ├── component │ │ │ ├── header.tsx │ │ │ ├── main-section.tsx │ │ │ └── footer.tsx │ │ ├── store.ts │ │ └── css │ │ │ └── base.css │ ├── .gitignore │ ├── typings │ │ └── global.d.ts │ ├── .vscode │ │ └── settings.json │ ├── jsconfig.json │ ├── .flowconfig │ ├── README.md │ ├── index.html │ ├── tsconfig.json │ ├── .babelrc │ ├── webpack.config.js │ ├── LICENSE │ └── package.json ├── AwesomeApp │ ├── .watchmanconfig │ ├── src │ │ ├── apps │ │ │ └── hello │ │ │ │ ├── webapi.ts │ │ │ │ ├── actor │ │ │ │ ├── counter-actor.ts │ │ │ │ └── hello-actor.ts │ │ │ │ ├── index.tsx │ │ │ │ ├── store.ts │ │ │ │ └── component │ │ │ │ └── hello.tsx │ │ └── uikit │ │ │ ├── noop.ts │ │ │ └── index.ts │ ├── .gitattributes │ ├── .babelrc │ ├── typings │ │ └── global.d.ts │ ├── .vscode │ │ └── settings.json │ ├── android │ │ ├── settings.gradle │ │ ├── app │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ └── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── awesomeapp │ │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ │ └── MainApplication.java │ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── BUCK │ │ │ └── proguard-rules.pro │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── keystores │ │ │ ├── debug.keystore.properties │ │ │ └── BUCK │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── gradlew.bat │ ├── react-dom │ │ ├── index.js │ │ └── package.json │ ├── .buckconfig │ ├── index.ios.js │ ├── __tests__ │ │ ├── index.ios.js │ │ └── index.android.js │ ├── tsconfig.json │ ├── ios │ │ ├── AwesomeApp │ │ │ ├── AppDelegate.h │ │ │ ├── main.m │ │ │ ├── Images.xcassets │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ ├── AppDelegate.m │ │ │ └── Info.plist │ │ ├── AwesomeAppTests │ │ │ ├── Info.plist │ │ │ └── AwesomeAppTests.m │ │ ├── AwesomeApp-tvOSTests │ │ │ └── Info.plist │ │ └── AwesomeApp-tvOS │ │ │ └── Info.plist │ ├── package.json │ ├── .gitignore │ ├── index.android.js │ └── .flowconfig ├── blog │ ├── webpack.production.js │ ├── typings │ │ └── global.d.ts │ ├── .vscode │ │ └── settings.json │ ├── __mock__ │ │ └── message.json │ ├── .flowconfig │ ├── README.md │ ├── apps │ │ ├── list │ │ │ ├── ql.ts │ │ │ ├── actor │ │ │ │ └── list-actor.ts │ │ │ ├── webapi.ts │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ └── component │ │ │ │ └── blog-list.tsx │ │ ├── detail │ │ │ ├── actor │ │ │ │ ├── loading-actor.ts │ │ │ │ └── detail-actor.ts │ │ │ ├── webapi.ts │ │ │ ├── index.tsx │ │ │ ├── component │ │ │ │ └── detail.tsx │ │ │ └── store.ts │ │ ├── edit │ │ │ ├── index.tsx │ │ │ ├── store.ts │ │ │ ├── actor │ │ │ │ └── blog-actor.ts │ │ │ └── component │ │ │ │ └── edit.tsx │ │ └── index.tsx │ ├── index.html │ ├── .babelrc │ ├── tsconfig.json │ ├── .gitignore │ ├── webpack.config.js │ └── package.json ├── iflux2Native │ ├── .watchmanconfig │ ├── js │ │ ├── home │ │ │ ├── webapi.js │ │ │ ├── index.js │ │ │ ├── actor │ │ │ │ ├── text-actor.js │ │ │ │ └── loading-actor.js │ │ │ ├── component │ │ │ │ └── title.js │ │ │ └── store.js │ │ └── setup.js │ ├── qmkit │ │ ├── index.js │ │ ├── index.js.flow │ │ ├── empty-fn.js │ │ └── package.json │ ├── android │ │ ├── settings.gradle │ │ ├── app │ │ │ ├── src │ │ │ │ └── main │ │ │ │ │ ├── res │ │ │ │ │ ├── values │ │ │ │ │ │ ├── strings.xml │ │ │ │ │ │ └── styles.xml │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ └── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── java │ │ │ │ │ └── com │ │ │ │ │ │ └── iflux2nativetemplate │ │ │ │ │ │ ├── MainActivity.java │ │ │ │ │ │ └── MainApplication.java │ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── BUCK │ │ │ └── proguard-rules.pro │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── keystores │ │ │ ├── debug.keystore.properties │ │ │ └── BUCK │ │ ├── build.gradle │ │ ├── gradle.properties │ │ └── gradlew.bat │ ├── .babelrc │ ├── react-dom │ │ ├── index.js │ │ └── package.json │ ├── .buckconfig │ ├── README.md │ ├── index.ios.js │ ├── index.android.js │ ├── package.json │ ├── ios │ │ ├── iflux2NativeTemplate │ │ │ ├── AppDelegate.h │ │ │ ├── main.m │ │ │ ├── Images.xcassets │ │ │ │ └── AppIcon.appiconset │ │ │ │ │ └── Contents.json │ │ │ ├── AppDelegate.m │ │ │ └── Info.plist │ │ └── iflux2NativeTemplateTests │ │ │ ├── Info.plist │ │ │ └── iflux2NativeTemplateTests.m │ └── .gitignore ├── timer │ ├── .gitignore │ ├── typings │ │ └── global.d.ts │ ├── .vscode │ │ └── settings.json │ ├── .flowconfig │ ├── README.md │ ├── index.html │ ├── __tests__ │ │ ├── tsconfig.json │ │ ├── __snapshots__ │ │ │ ├── store.test.ts.snap │ │ │ └── time.test.tsx.snap │ │ ├── store.test.ts │ │ ├── time.test.tsx │ │ └── timer-actor.test.ts │ ├── tsconfig.json │ ├── src │ │ ├── actor │ │ │ └── timer-actor.ts │ │ ├── index.tsx │ │ ├── store.ts │ │ └── component │ │ │ └── timer.tsx │ ├── .babelrc │ ├── webpack.config.js │ └── package.json ├── counter │ ├── .gitignore │ ├── typings │ │ └── global.d.ts │ ├── .flowconfig │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── src │ │ ├── ql.ts │ │ ├── actor │ │ │ └── counter-actor.ts │ │ ├── index.tsx │ │ ├── store.ts │ │ └── component │ │ │ └── counter.tsx │ ├── index.html │ ├── __tests__ │ │ ├── tsconfig.json │ │ ├── __snapshots__ │ │ │ ├── store.test.ts.snap │ │ │ └── counter.test.tsx.snap │ │ ├── ql.test.ts │ │ ├── store.test.ts │ │ ├── counter.test.tsx │ │ └── counter-actor.test.ts │ ├── tsconfig.json │ ├── .babelrc │ ├── webpack.config.js │ └── package.json └── validator │ ├── .vscode │ └── settings.json │ ├── .flowconfig │ ├── index.html │ ├── README.md │ ├── src │ ├── components │ │ ├── button-field.tsx │ │ └── form-field.tsx │ ├── index.tsx │ ├── actor │ │ ├── user-actor.ts │ │ └── validate-field-actor.ts │ ├── store.ts │ └── ql.ts │ ├── tsconfig.json │ ├── .babelrc │ ├── webpack.config.js │ └── package.json ├── documents ├── gitbook │ ├── redux.md │ ├── react.md │ ├── SUMMARY.md │ ├── relax.md │ ├── README.md │ ├── store-provider.md │ ├── actor.md │ ├── store.md │ ├── ql.md │ ├── dql.md │ └── example.md └── README.md ├── src ├── msg.ts ├── index.ts ├── contrib │ ├── util.ts │ └── atom.ts ├── actor.ts ├── decorator.ts ├── ql.ts ├── util.ts └── dql.ts ├── react-dom ├── index.js └── package.json ├── __tests__ ├── __snapshots__ │ ├── test-relax.tsx.snap │ ├── test-multiple-store-context.tsx.snap │ └── test-react-dql.tsx.snap ├── tsconfig.json ├── test-msg.ts ├── yarn.lock ├── test-ql.ts ├── test-dql.ts ├── test-merge.ts ├── test-relax.tsx ├── test-actor.ts ├── test-transaction-rollback.ts ├── test-util.ts ├── test-atom.ts ├── test-react-dql.tsx └── transaction-rollback-test.ts ├── .flowconfig ├── .babelrc ├── types ├── util.d.ts ├── util.js.flow ├── atom.d.ts ├── atom.js.flow ├── validator.js.flow ├── validator.d.ts ├── index.js.flow └── index.d.ts ├── scripts └── type.sh ├── .vscode └── settings.json ├── tsconfig.json ├── .gitignore ├── perf └── reduce-state.perf.js ├── LICENSE └── package.json /examples/todo/src/webapi.ts: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/AwesomeApp/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /examples/blog/webpack.production.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/iflux2Native/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /examples/iflux2Native/js/home/webapi.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/timer/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/todo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/AwesomeApp/src/apps/hello/webapi.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/counter/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /examples/AwesomeApp/.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /documents/gitbook/redux.md: -------------------------------------------------------------------------------- 1 | # vs redux 2 | 3 | @zhangfeng 4 | -------------------------------------------------------------------------------- /examples/AwesomeApp/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"] 3 | } -------------------------------------------------------------------------------- /examples/blog/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; -------------------------------------------------------------------------------- /examples/counter/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; -------------------------------------------------------------------------------- /examples/timer/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; -------------------------------------------------------------------------------- /examples/todo/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; -------------------------------------------------------------------------------- /examples/AwesomeApp/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; -------------------------------------------------------------------------------- /examples/AwesomeApp/src/uikit/noop.ts: -------------------------------------------------------------------------------- 1 | export default function noop() { 2 | 3 | } -------------------------------------------------------------------------------- /examples/blog/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false 3 | } -------------------------------------------------------------------------------- /examples/timer/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false 3 | } -------------------------------------------------------------------------------- /examples/todo/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false 3 | } -------------------------------------------------------------------------------- /examples/AwesomeApp/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false 3 | } -------------------------------------------------------------------------------- /examples/blog/__mock__/message.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "new world, new iflux2!" 3 | } -------------------------------------------------------------------------------- /examples/validator/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false 3 | } -------------------------------------------------------------------------------- /src/msg.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | export default new EventEmitter(); 4 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'AwesomeApp' 2 | 3 | include ':app' 4 | -------------------------------------------------------------------------------- /examples/iflux2Native/qmkit/index.js: -------------------------------------------------------------------------------- 1 | import emptyFn from './empty-fn' 2 | 3 | export { 4 | emptyFn 5 | }; 6 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'iflux2NativeTemplate' 2 | 3 | include ':app' 4 | -------------------------------------------------------------------------------- /examples/blog/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | esproposal.decorators=ignore 9 | -------------------------------------------------------------------------------- /examples/counter/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | esproposal.decorators=ignore 9 | -------------------------------------------------------------------------------- /examples/timer/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | esproposal.decorators=ignore 9 | -------------------------------------------------------------------------------- /examples/validator/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | esproposal.decorators=ignore 9 | -------------------------------------------------------------------------------- /react-dom/index.js: -------------------------------------------------------------------------------- 1 | import {unstable_batchedUpdates} from 'react-native'; 2 | 3 | export { 4 | unstable_batchedUpdates 5 | }; 6 | -------------------------------------------------------------------------------- /examples/blog/README.md: -------------------------------------------------------------------------------- 1 | # iflux2-blog 2 | 3 | ```sh 4 | yarn 5 | yarn start 6 | ``` 7 | # visit 8 | 9 | http://localhost:3000 10 | -------------------------------------------------------------------------------- /examples/counter/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": false, 3 | "typescript.tsdk": "./node_modules/typescript/lib" 4 | } -------------------------------------------------------------------------------- /examples/iflux2Native/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react-native"], 3 | "plugins":[ 4 | "transform-decorators-legacy" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /documents/gitbook/react.md: -------------------------------------------------------------------------------- 1 | # React 2 | 3 | iflux2中,我们思考了很多,最后通过两个decorator完成了Store和React组件的沟通结合 4 | 5 | 6 | @StorePrivder 7 | 8 | @Relax 9 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AwesomeApp 3 | 4 | -------------------------------------------------------------------------------- /examples/AwesomeApp/src/uikit/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @providesModule uikit 3 | */ 4 | 5 | import noop from './noop' 6 | 7 | export { 8 | noop 9 | } -------------------------------------------------------------------------------- /examples/iflux2Native/qmkit/index.js.flow: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | //空函数 4 | declare function isEmpty(): void; 5 | 6 | export { 7 | isEmpty, 8 | } 9 | -------------------------------------------------------------------------------- /examples/AwesomeApp/react-dom/index.js: -------------------------------------------------------------------------------- 1 | import {unstable_batchedUpdates} from 'react-native'; 2 | 3 | export { 4 | unstable_batchedUpdates 5 | }; 6 | -------------------------------------------------------------------------------- /examples/iflux2Native/js/setup.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import HomeApp from './home' 4 | 5 | const setUp = () => HomeApp 6 | 7 | export default setUp; 8 | -------------------------------------------------------------------------------- /examples/iflux2Native/react-dom/index.js: -------------------------------------------------------------------------------- 1 | import {unstable_batchedUpdates} from 'react-native'; 2 | 3 | export { 4 | unstable_batchedUpdates 5 | }; 6 | -------------------------------------------------------------------------------- /examples/timer/README.md: -------------------------------------------------------------------------------- 1 | ### iflux2-counter 2 | 3 | ```sh 4 | yarn 5 | yarn start 6 | ``` 7 | 8 | ### browser 9 | http://localhost:3000/ 10 | -------------------------------------------------------------------------------- /examples/counter/README.md: -------------------------------------------------------------------------------- 1 | ### iflux2-counter 2 | 3 | ```sh 4 | yarn 5 | yarn start 6 | ``` 7 | 8 | ### browser 9 | http://localhost:3000/ 10 | -------------------------------------------------------------------------------- /examples/blog/apps/list/ql.ts: -------------------------------------------------------------------------------- 1 | import { QL } from 'iflux2' 2 | 3 | export const helloQL = QL('helloQL', [ 4 | 'msg', 5 | (msg) => `${msg} from ql` 6 | ]) 7 | -------------------------------------------------------------------------------- /examples/counter/src/ql.ts: -------------------------------------------------------------------------------- 1 | import { QL } from 'iflux2' 2 | 3 | export const countQL = QL('countQL', [ 4 | 'count', 5 | (count) => `QL:${count}` 6 | ]); 7 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | iflux2NativeTemplate 3 | 4 | -------------------------------------------------------------------------------- /examples/todo/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "es2015", 5 | "experimentalDecorators": true 6 | } 7 | } -------------------------------------------------------------------------------- /examples/AwesomeApp/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /examples/iflux2Native/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /examples/iflux2Native/README.md: -------------------------------------------------------------------------------- 1 | # iflux2-native-template 2 | iflux2 reactnative 模板 3 | 4 | # Get Started 5 | ```sh 6 | npm install 7 | react-native run-ios 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/AwesomeApp/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/todo/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | esproposal.class_static_fields=enable 9 | esproposal.decorators=ignore 10 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/iflux2Native/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /examples/AwesomeApp/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/keystores/debug.keystore.properties: -------------------------------------------------------------------------------- 1 | key.store=debug.keystore 2 | key.alias=androiddebugkey 3 | key.store.password=android 4 | key.alias.password=android 5 | -------------------------------------------------------------------------------- /examples/todo/README.md: -------------------------------------------------------------------------------- 1 | # iflux2-todo 2 | a simple todomvc (iflux2) 3 | 4 | 5 | ## get started 6 | ```sh 7 | yarn 8 | yarn start 9 | ``` 10 | 11 | http://localhost:3000 12 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/test-relax.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test store provider and relax 1`] = ` 4 |
5 | hello iflux2 6 |
7 | `; 8 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/AwesomeApp/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/AwesomeApp/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/AwesomeApp/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/AwesomeApp/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/iflux2Native/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/iflux2Native/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/iflux2Native/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/QianmiOpen/iflux2/HEAD/examples/iflux2Native/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /examples/iflux2Native/qmkit/empty-fn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * do nothing 3 | * @providesModule empty-fn 4 | */ 5 | function noop() { 6 | //do nothing 7 | } 8 | 9 | //expose 10 | export default noop; 11 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/keystores/BUCK: -------------------------------------------------------------------------------- 1 | keystore( 2 | name = 'debug', 3 | store = 'debug.keystore', 4 | properties = 'debug.keystore.properties', 5 | visibility = [ 6 | 'PUBLIC', 7 | ], 8 | ) 9 | -------------------------------------------------------------------------------- /examples/blog/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | hello iflux2 4 | 5 | 6 |
7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/AwesomeApp/index.ios.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { AppRegistry } from 'react-native'; 3 | import Hello from './js/apps/hello' 4 | 5 | const app = () => Hello 6 | 7 | AppRegistry.registerComponent('AwesomeApp', app); 8 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/examples/** 3 | .*/node_modules/fbjs/lib/partitionObjectByKey.js.flow 4 | 5 | [include] 6 | 7 | [libs] 8 | 9 | [options] 10 | esproposal.class_static_fields=enable 11 | esproposal.class_instance_fields=enable 12 | esproposal.decorators=ignore -------------------------------------------------------------------------------- /__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "experimentalDecorators": true, 8 | "jsx": "react" 9 | } 10 | } -------------------------------------------------------------------------------- /__tests__/test-msg.ts: -------------------------------------------------------------------------------- 1 | import msg from '../src/msg'; 2 | 3 | 4 | describe('msg test suite', () => { 5 | it('msg echo', () => { 6 | msg.on('ping', (hello) => { 7 | expect('hello').toEqual(hello); 8 | }); 9 | 10 | msg.emit('ping', 'hello'); 11 | }); 12 | }); -------------------------------------------------------------------------------- /__tests__/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/jest@^18.1.1": 6 | version "18.1.1" 7 | resolved "https://registry.yarnpkg.com/@types/jest/-/jest-18.1.1.tgz#6f63488c64726900885ab9cd5697bb7fa1b416cc" 8 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/counter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | counter app 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/timer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | counter app 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"], 3 | "plugins":[ 4 | "transform-decorators-legacy", 5 | "transform-es2015-modules-commonjs", 6 | "transform-flow-strip-types", 7 | "transform-class-properties", 8 | "syntax-object-rest-spread" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 6 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip 6 | -------------------------------------------------------------------------------- /examples/counter/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "noImplicitAny": false, 8 | "sourceMap": false 9 | } 10 | } -------------------------------------------------------------------------------- /examples/timer/__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es5", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "experimentalDecorators": true, 8 | "jsx": "react" 9 | } 10 | } -------------------------------------------------------------------------------- /examples/validator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | iflux2-validator-app 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /types/util.d.ts: -------------------------------------------------------------------------------- 1 | 2 | export = Util; 3 | 4 | declare namespace Util { 5 | function isArray(arr: any): boolean; 6 | function isFn(fn: any): boolean; 7 | function isStr(str: any): boolean; 8 | function isObject(obj: any): boolean; 9 | function type(t: any): string; 10 | } 11 | -------------------------------------------------------------------------------- /examples/blog/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-3"], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | "transform-es2015-modules-commonjs", 6 | "transform-class-properties", 7 | "transform-flow-strip-types", 8 | "transform-runtime" 9 | ] 10 | } -------------------------------------------------------------------------------- /examples/iflux2Native/index.ios.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | import { AppRegistry } from 'react-native' 7 | import setUp from './js/setup' 8 | 9 | AppRegistry.registerComponent('iflux2NativeTemplate', setUp); 10 | -------------------------------------------------------------------------------- /examples/iflux2Native/index.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | import { AppRegistry } from 'react-native' 7 | import setUp from './js/setup' 8 | 9 | AppRegistry.registerComponent('iflux2NativeTemplate', setUp); 10 | -------------------------------------------------------------------------------- /scripts/type.sh: -------------------------------------------------------------------------------- 1 | cp types/index.js.flow dist 2 | cp types/index.d.ts dist 3 | 4 | cp types/atom.js.flow contrib 5 | cp types/validator.js.flow contrib 6 | cp types/util.js.flow contrib 7 | 8 | cp types/atom.d.ts contrib 9 | cp types/validator.d.ts contrib 10 | cp types/util.d.ts contrib 11 | 12 | -------------------------------------------------------------------------------- /examples/iflux2Native/qmkit/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qmkit", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC" 12 | } 13 | -------------------------------------------------------------------------------- /examples/validator/README.md: -------------------------------------------------------------------------------- 1 | iflux2-validator demo 2 | 3 | 思路: 4 | 5 | 不在校验dom,而是校验我们的领域对象(domain object), 6 | view只是显示了我们的校验结果而已。 7 | 8 | 目标: 声明式的校验表达 9 | 10 | 1. 校验全部配置的数据 11 | 2. 校验配置数据中的部分数据 12 | 4. 异步校验使用正常业务逻辑流转 13 | 14 | ```sh 15 | yarn 16 | yarn start 17 | ``` 18 | 19 | http://localhost:3000/ 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | "editor.tabCompletion": true, 4 | "editor.tabSize": 2, 5 | "editor.formatOnSave": true, 6 | "editor.formatOnPaste": true, 7 | "editor.formatOnType": true, 8 | "editor.rulers": [ 9 | 80 10 | ] 11 | } -------------------------------------------------------------------------------- /examples/timer/__tests__/__snapshots__/store.test.ts.snap: -------------------------------------------------------------------------------- 1 | exports[`store store decrement 1`] = ` 2 | Object { 3 | "time": 0, 4 | } 5 | `; 6 | 7 | exports[`store store increment 1`] = ` 8 | Object { 9 | "time": 1, 10 | } 11 | `; 12 | 13 | exports[`store store init state 1`] = ` 14 | Object { 15 | "time": 0, 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /examples/todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | iflux2 • TodoMVC 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/todo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "noImplicitAny": false, 8 | "sourceMap": false 9 | }, 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } -------------------------------------------------------------------------------- /examples/validator/src/components/button-field.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export default class Button extends React.Component { 4 | render() { 5 | return ( 6 | 7 | 8 | {this.props.children} 9 | 10 | 11 | ) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/blog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "rootDir": "apps", 10 | "outDir": "build" 11 | } 12 | } -------------------------------------------------------------------------------- /examples/counter/__tests__/__snapshots__/store.test.ts.snap: -------------------------------------------------------------------------------- 1 | exports[`store store decrement 1`] = ` 2 | Object { 3 | "count": -1, 4 | } 5 | `; 6 | 7 | exports[`store store increment 1`] = ` 8 | Object { 9 | "count": 1, 10 | } 11 | `; 12 | 13 | exports[`store store init state 1`] = ` 14 | Object { 15 | "count": 0, 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /examples/validator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "rootDir": "src", 10 | "outDir": "build" 11 | } 12 | } -------------------------------------------------------------------------------- /examples/AwesomeApp/src/apps/hello/actor/counter-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from "iflux2"; 2 | 3 | export default class CounterActor extends Actor { 4 | defaultState() { 5 | return { count: 1 } 6 | } 7 | 8 | @Action('like') 9 | like(state: IMap) { 10 | return state.update('count', count => count + 1) 11 | } 12 | } -------------------------------------------------------------------------------- /examples/AwesomeApp/src/apps/hello/actor/hello-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | 3 | export default class HelloActor extends Actor { 4 | defaultState() { 5 | return { text: 'hello iflux2!' } 6 | } 7 | 8 | @Action('change') 9 | change(state: IMap, text) { 10 | return state.set('text', text) 11 | } 12 | } -------------------------------------------------------------------------------- /react-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-dom", 3 | "version": "1.0.0", 4 | "description": "mock react-dom in react-native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": ["react-native", "react-dom"], 10 | "author": "", 11 | "license": "BSD" 12 | } 13 | -------------------------------------------------------------------------------- /documents/gitbook/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [iflux](iflux.md) 5 | * [iflux2](iflux2.md) 6 | * [Store](store.md) 7 | * [Actor](actor.md) 8 | * [QL](ql.md) 9 | * [DQL](dql.md) 10 | * [React]() 11 | * [StoreProvider](store-provider.md) 12 | * [Relax](relax.md) 13 | * [vs redux](redux.md) 14 | * [examples](example.md) 15 | -------------------------------------------------------------------------------- /examples/blog/apps/list/actor/list-actor.ts: -------------------------------------------------------------------------------- 1 | import { Action, Actor, IMap } from 'iflux2' 2 | 3 | export default class ListActor extends Actor { 4 | defaultState() { 5 | return { 6 | blogs: [] 7 | } 8 | } 9 | 10 | @Action('init') 11 | init(state: IMap, blogs: Array) { 12 | return state.set('blogs', blogs) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/AwesomeApp/__tests__/index.ios.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.ios.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/AwesomeApp/react-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-dom", 3 | "version": "1.0.0", 4 | "description": "mock react-dom in react-native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": ["react-native", "react-dom"], 10 | "author": "", 11 | "license": "BSD" 12 | } 13 | -------------------------------------------------------------------------------- /types/util.js.flow: -------------------------------------------------------------------------------- 1 | //@flow 2 | declare function isArray(arr: mixed): boolean; 3 | declare function isFn(fn: mixed): boolean; 4 | declare function isStr(str: mixed): boolean; 5 | declare function isObject(obj: mixed): boolean; 6 | declare function type(t: mixed): string; 7 | 8 | export { 9 | isArray, 10 | isFn, 11 | isStr, 12 | isObject, 13 | type 14 | }; 15 | -------------------------------------------------------------------------------- /examples/blog/apps/detail/actor/loading-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | 3 | export default class LoadingActor extends Actor { 4 | defaultState() { 5 | return { 6 | loading: true 7 | } 8 | } 9 | 10 | @Action('loading') 11 | loading(state: IMap, status: boolean) { 12 | return state.set('loading', status) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/iflux2Native/react-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-dom", 3 | "version": "1.0.0", 4 | "description": "mock react-dom in react-native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": ["react-native", "react-dom"], 10 | "author": "", 11 | "license": "BSD" 12 | } 13 | -------------------------------------------------------------------------------- /types/atom.d.ts: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | 3 | export = Atom; 4 | 5 | declare class Atom { 6 | constructor(record: Object); 7 | value(path: string | Array): Atom.IState; 8 | subscribe(callback: Function): void; 9 | unsubscribe(callback: Function): void; 10 | } 11 | 12 | declare namespace Atom { 13 | export type IState = Map; 14 | } -------------------------------------------------------------------------------- /__tests__/test-ql.ts: -------------------------------------------------------------------------------- 1 | import {QL} from '../src/ql'; 2 | 3 | 4 | //QL的name改为必填项 5 | const testQL = QL('testQL', [ 6 | ['user'], 7 | (user) => user 8 | ]); 9 | 10 | 11 | describe('query lang test suite', () => { 12 | it('it should be query lang', () => { 13 | expect(true).toEqual(testQL.isValidQuery()); 14 | expect(1).toEqual(testQL.id()); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /examples/AwesomeApp/__tests__/index.android.js: -------------------------------------------------------------------------------- 1 | import 'react-native'; 2 | import React from 'react'; 3 | import Index from '../index.android.js'; 4 | 5 | // Note: test renderer must be required after react-native. 6 | import renderer from 'react-test-renderer'; 7 | 8 | it('renders correctly', () => { 9 | const tree = renderer.create( 10 | 11 | ); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/blog/apps/list/webapi.ts: -------------------------------------------------------------------------------- 1 | //@flow 2 | import 'whatwg-fetch' 3 | 4 | export const fetchMsg = (): Promise => { 5 | return new Promise(resolve => { 6 | //稍微延时一下 7 | setTimeout(() => { 8 | fetch('http://localhost:3000/__mock__/message.json') 9 | .then((res) => res.json()) 10 | .then(res => resolve(res)) 11 | }, 500) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /examples/iflux2Native/js/home/index.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import React, { Component } from 'react' 4 | import { StoreProvider } from 'iflux2' 5 | 6 | import Title from './component/title' 7 | import AppStore from './store' 8 | 9 | @StoreProvider(AppStore, {debug: true}) 10 | export default class HomeApp extends Component { 11 | render() { 12 | return 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /types/atom.js.flow: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import {Map} from 'immutable' 4 | 5 | //Immutable State 6 | export type IState = Map<string, any>; 7 | 8 | declare class Atom { 9 | constructor(record: Object): void; 10 | value(path: string|Array<String>): IState; 11 | subscribe(callback: Function): void; 12 | unsubscribe(callback: Function): void; 13 | } 14 | 15 | export default Atom; 16 | -------------------------------------------------------------------------------- /examples/blog/apps/edit/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Edit from './component/edit' 3 | import { StoreProvider } from 'iflux2' 4 | import AppStore from './store' 5 | 6 | 7 | @StoreProvider(AppStore, { debug: true }) 8 | export default class BlogEdit extends React.Component<any, any> { 9 | render() { 10 | return ( 11 | <Edit /> 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/AwesomeApp/src/apps/hello/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "React" 2 | import { StoreProvider } from 'iflux2' 3 | import AppStore from "./store" 4 | import Hello from "./component/hello"; 5 | 6 | 7 | @StoreProvider(AppStore, { debug: true }) 8 | export default class HelloApp extends React.Component<any, any> { 9 | render() { 10 | return ( 11 | <Hello /> 12 | ) 13 | } 14 | } -------------------------------------------------------------------------------- /examples/blog/apps/detail/webapi.ts: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable' 2 | 3 | export const fetchDetail = (id: number): Promise<Object> => { 4 | return new Promise((resolve) => { 5 | setTimeout(() => { 6 | //flow is so cool, it found a null exception. 7 | resolve( 8 | fromJS(JSON.parse(localStorage.getItem(`blog@${id}`) || '')) 9 | ) 10 | }, 200) 11 | }) 12 | }; 13 | -------------------------------------------------------------------------------- /examples/iflux2Native/js/home/actor/text-actor.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | 'use strict;' 4 | 5 | import { Action, Actor } from 'iflux2' 6 | 7 | export default class TextActor extends Actor { 8 | defaultState() { 9 | return { 10 | text: 'Hello, ReactNative!!!' 11 | } 12 | } 13 | 14 | @Action('change') 15 | change(state, text) { 16 | return state.set('text', text) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/blog/apps/detail/actor/detail-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | 3 | export default class DetailActor extends Actor { 4 | defaultState() { 5 | return { 6 | id: 0, 7 | title: '', 8 | content: '', 9 | createAt: '' 10 | } 11 | } 12 | 13 | @Action('init') 14 | init(state: IMap, blog: Object) { 15 | return state.merge(blog) 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /examples/timer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "noImplicitAny": false, 8 | "sourceMap": false, 9 | "rootDir": "src", 10 | "outDir": "build" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "__tests__" 15 | ] 16 | } -------------------------------------------------------------------------------- /examples/counter/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "experimentalDecorators": true, 6 | "jsx": "react", 7 | "noImplicitAny": false, 8 | "sourceMap": false, 9 | "rootDir": "src", 10 | "outDir": "build" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "__tests__" 15 | ] 16 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "rootDir": "src", 8 | "jsx": "react", 9 | "noUnusedLocals": true, 10 | "outDir": "./build" 11 | }, 12 | "exclude": [ 13 | "node_modules", 14 | "examples", 15 | "__tests__" 16 | ] 17 | } -------------------------------------------------------------------------------- /examples/iflux2Native/js/home/actor/loading-actor.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | import { Actor, Action } from 'iflux2' 4 | 5 | import type {ActorState} from 'iflux2' 6 | 7 | export default class LoadingActor extends Actor { 8 | defaultState() { 9 | return { 10 | loading: true, 11 | } 12 | } 13 | 14 | @Action('loading:success') 15 | loadingSuccess(state: ActorState) { 16 | return state.set('loading', false) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/timer/src/actor/timer-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | 3 | export default class CounterActor extends Actor { 4 | defaultState() { 5 | return { 6 | time: 0 7 | } 8 | } 9 | 10 | @Action('start') 11 | increment(state: IMap) { 12 | return state.update('time', time => time + 1) 13 | } 14 | 15 | @Action('reset') 16 | reset(state: IMap) { 17 | return state.set('time', 0) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/blog/apps/list/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { StoreProvider } from 'iflux2' 3 | import AppStore from './store' 4 | import BlogList from './component/blog-list' 5 | 6 | @StoreProvider(AppStore, { debug: true }) 7 | export default class BlogListApp extends React.Component<any, any> { 8 | componentDidMount() { 9 | this.props.store.init() 10 | } 11 | 12 | render() { 13 | return ( 14 | <BlogList /> 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/counter/src/actor/counter-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | 3 | export default class CounterActor extends Actor { 4 | defaultState() { 5 | return { count: 0 } 6 | } 7 | 8 | @Action('increment') 9 | increment(state: IMap) { 10 | return state.update('count', count => count + 1) 11 | } 12 | 13 | @Action('decrement') 14 | decrement(state: IMap) { 15 | return state.update('count', count => count - 1) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/timer/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import { StoreProvider } from 'iflux2' 4 | import AppStore from './store' 5 | import Timer from './component/timer' 6 | 7 | @StoreProvider(AppStore, { debug: __DEV__ }) 8 | export default class TimerApp extends React.Component<any, any> { 9 | render() { 10 | return <Timer /> 11 | } 12 | } 13 | 14 | ReactDOM.render(<TimerApp />, document.getElementById('app')) 15 | -------------------------------------------------------------------------------- /examples/counter/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | import { StoreProvider } from 'iflux2' 4 | import AppStore from './store' 5 | import Counter from './component/counter' 6 | 7 | @StoreProvider(AppStore, { debug: __DEV__ }) 8 | export default class CounterApp extends React.Component<any, any> { 9 | render() { 10 | return <Counter /> 11 | } 12 | } 13 | 14 | ReactDOM.render(<CounterApp />, document.getElementById('app')) 15 | -------------------------------------------------------------------------------- /examples/validator/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { render } from 'react-dom' 3 | import { StoreProvider } from 'iflux2' 4 | import AppStore from './store' 5 | import Form from './components/form' 6 | 7 | 8 | @StoreProvider(AppStore, { debug: true }) 9 | class ValidatorApp extends React.Component<any, any> { 10 | render() { 11 | return ( 12 | <Form /> 13 | ) 14 | } 15 | } 16 | 17 | render(<ValidatorApp />, document.getElementById('app')); 18 | -------------------------------------------------------------------------------- /examples/blog/apps/detail/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { StoreProvider } from 'iflux2' 3 | import AppStore from './store' 4 | import Detail from './component/detail' 5 | 6 | @StoreProvider(AppStore, { debug: true }) 7 | export default class BlogDetail extends React.Component<any, any>{ 8 | componentDidMount() { 9 | this.props.store.init(this.props.params.id) 10 | } 11 | 12 | render() { 13 | return ( 14 | <Detail /> 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/counter/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-3"], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | "transform-class-properties", 6 | "transform-es2015-modules-commonjs", 7 | "transform-flow-strip-types", 8 | "transform-runtime" 9 | ], 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/timer/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-3"], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | "transform-class-properties", 6 | "transform-es2015-modules-commonjs", 7 | "transform-flow-strip-types", 8 | "transform-runtime" 9 | ], 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/todo/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-3"], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | "transform-class-properties", 6 | "transform-es2015-modules-commonjs", 7 | "transform-flow-strip-types", 8 | "transform-runtime" 9 | ], 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import msg from './msg'; 2 | import Actor from './actor'; 3 | import StoreProvider from './store-provider'; 4 | import Relax from './relax'; 5 | import Store from './store'; 6 | import { Action, CtxStoreName } from './decorator'; 7 | import { QL } from './ql'; 8 | import { DQL } from './dql'; 9 | 10 | export { 11 | QL, 12 | DQL, 13 | 14 | msg, 15 | 16 | Action, 17 | Actor, 18 | 19 | CtxStoreName, 20 | Relax, 21 | 22 | Store, 23 | StoreProvider 24 | }; 25 | -------------------------------------------------------------------------------- /examples/iflux2Native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iflux2Native", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "postinstall": "npm install ./react-dom" 8 | }, 9 | "dependencies": { 10 | "iflux2": "1.4.4", 11 | "react": "15.4.2", 12 | "react-native": "0.39.2" 13 | }, 14 | "devDependencies": { 15 | "babel-plugin-transform-decorators-legacy": "^1.3.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/validator/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react", "stage-3"], 3 | "plugins": [ 4 | "transform-decorators-legacy", 5 | "transform-class-properties", 6 | "transform-es2015-modules-commonjs", 7 | "transform-flow-strip-types", 8 | "transform-runtime" 9 | ], 10 | "env": { 11 | "production": { 12 | "plugins": [ 13 | "transform-react-constant-elements", 14 | "transform-react-inline-elements" 15 | ] 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/java/com/awesomeapp/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.awesomeapp; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "AwesomeApp"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/counter/__tests__/ql.test.ts: -------------------------------------------------------------------------------- 1 | import Store from '../src/store' 2 | import { countQL } from '../src/ql' 3 | 4 | describe('ql', () => { 5 | it('countQL', () => { 6 | expect(countQL.isValidQuery()).toEqual(true) 7 | 8 | const store = new Store({}) 9 | expect(store.bigQuery(countQL)).toEqual('QL:0') 10 | 11 | store.increment(); 12 | expect(store.bigQuery(countQL)).toEqual('QL:1'); 13 | 14 | store.decrement(); 15 | expect(store.bigQuery(countQL)).toEqual('QL:0'); 16 | }); 17 | }) 18 | -------------------------------------------------------------------------------- /examples/todo/src/actor/filter-actor.ts: -------------------------------------------------------------------------------- 1 | import { Action, Actor, IMap } from 'iflux2' 2 | 3 | export default class FilterActor extends Actor { 4 | defaultState() { 5 | return { 6 | filterStatus: '' //默认没有过滤条件 7 | } 8 | } 9 | 10 | @Action('filter') 11 | changeFilter(state: IMap, status: string) { 12 | return state.set('filterStatus', status) 13 | } 14 | 15 | @Action('init') 16 | init(state: IMap, filterStatus: string) { 17 | return state.set('filterStatus', filterStatus) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/AwesomeApp/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "jsx": "react-native", 8 | "experimentalDecorators": true, 9 | "baseUrl": "./src", 10 | "rootDir": "./src", 11 | "outDir": "js" 12 | }, 13 | "exclude": [ 14 | "node_modules", 15 | "__tests__", 16 | "js", 17 | "android", 18 | "ios" 19 | ] 20 | } -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/java/com/iflux2nativetemplate/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.iflux2nativetemplate; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. 9 | * This is used to schedule rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "iflux2NativeTemplate"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import <UIKit/UIKit.h> 11 | 12 | @interface AppDelegate : UIResponder <UIApplicationDelegate> 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /examples/counter/src/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import CounterActor from './actor/counter-actor' 3 | 4 | export default class AppStore extends Store { 5 | bindActor() { 6 | return [ 7 | new CounterActor 8 | ] 9 | } 10 | 11 | constructor(props: IOptions) { 12 | super(props) 13 | if (__DEV__) { 14 | window['store'] = this 15 | } 16 | } 17 | 18 | increment = () => { 19 | this.dispatch('increment') 20 | }; 21 | 22 | decrement = () => { 23 | this.dispatch('decrement') 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplate/AppDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import <UIKit/UIKit.h> 11 | 12 | @interface AppDelegate : UIResponder <UIApplicationDelegate> 13 | 14 | @property (nonatomic, strong) UIWindow *window; 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /examples/todo/src/actor/text-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2'; 2 | 3 | export default class TextActor extends Actor { 4 | defaultState() { 5 | return { 6 | value: '' 7 | }; 8 | } 9 | 10 | @Action('changeValue') 11 | changeValue(state: IMap, value: string) { 12 | return state.set('value', value); 13 | } 14 | 15 | @Action('submit') 16 | submit(state: IMap) { 17 | return state.set('value', '') 18 | } 19 | 20 | @Action('init') 21 | init(state: IMap, value: string) { 22 | return state.set('value', value) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/AwesomeApp/src/apps/hello/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import HelloActor from "./actor/hello-actor" 3 | import CounterActor from "./actor/counter-actor" 4 | 5 | export default class AppStore extends Store { 6 | constructor(props: IOptions) { 7 | super(props) 8 | if (__DEV__) { 9 | //debug 10 | window['hello'] = this 11 | } 12 | } 13 | 14 | bindActor() { 15 | return [ 16 | new HelloActor, 17 | new CounterActor, 18 | ] 19 | } 20 | 21 | like = (text: string) => { 22 | this.dispatch('like') 23 | }; 24 | } -------------------------------------------------------------------------------- /examples/timer/__tests__/store.test.ts: -------------------------------------------------------------------------------- 1 | import Store from '../src/store' 2 | 3 | describe('store', () => { 4 | it('store init state', () => { 5 | const store = new Store({}) 6 | const state = store.state() 7 | expect(state).toMatchSnapshot() 8 | }) 9 | 10 | it('store increment', () => { 11 | const store = new Store({}) 12 | store.dispatch('start') 13 | expect(store.state()).toMatchSnapshot() 14 | }) 15 | 16 | it('store decrement', () => { 17 | const store = new Store({}) 18 | store.reset() 19 | expect(store.state()).toMatchSnapshot() 20 | }) 21 | }) -------------------------------------------------------------------------------- /examples/counter/__tests__/store.test.ts: -------------------------------------------------------------------------------- 1 | import Store from '../src/store' 2 | 3 | describe('store', () => { 4 | it('store init state', () => { 5 | const store = new Store({}) 6 | const state = store.state() 7 | expect(state).toMatchSnapshot() 8 | }) 9 | 10 | it('store increment', () => { 11 | const store = new Store({}) 12 | store.increment() 13 | expect(store.state()).toMatchSnapshot() 14 | }) 15 | 16 | it('store decrement', () => { 17 | const store = new Store({}) 18 | store.decrement() 19 | expect(store.state()).toMatchSnapshot() 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import <UIKit/UIKit.h> 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/test-multiple-store-context.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`test multiple store context binding context 1`] = ` 4 | <div> 5 | hello 6 | <div> 7 | 0 8 | </div> 9 | </div> 10 | `; 11 | 12 | exports[`test multiple store context multiple context 1`] = ` 13 | <div> 14 | hello 15 | <div> 16 | 1 17 | </div> 18 | </div> 19 | `; 20 | 21 | exports[`test multiple store context test CounterApp 1`] = ` 22 | <div> 23 | 1 24 | </div> 25 | `; 26 | 27 | exports[`test multiple store context test HelloApp 1`] = ` 28 | <div> 29 | hello 30 | </div> 31 | `; 32 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplate/main.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import <UIKit/UIKit.h> 11 | 12 | #import "AppDelegate.h" 13 | 14 | int main(int argc, char * argv[]) { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/blog/apps/detail/component/detail.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax } from 'iflux2' 3 | 4 | @Relax 5 | export default class Detail extends React.Component<any, any> { 6 | props: { 7 | title?: string; 8 | createAt?: string; 9 | content?: string; 10 | }; 11 | 12 | static defaultProps = { 13 | title: '', 14 | createAt: '', 15 | content: '' 16 | }; 17 | 18 | render() { 19 | const { title, createAt, content } = this.props 20 | 21 | return ( 22 | <div> 23 | <h3>{title}</h3> 24 | <h4>{createAt}</h4> 25 | <p>{content}</p> 26 | </div> 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/validator/src/actor/user-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | import { fromJS } from 'immutable' 3 | 4 | type Field = { name: string; value: string; } 5 | 6 | const init = { 7 | username: '', 8 | password: '', 9 | confirm: '', 10 | email: '', 11 | qq: '' 12 | }; 13 | 14 | export default class UserActor extends Actor { 15 | defaultState() { 16 | return init 17 | } 18 | 19 | @Action('changeValue') 20 | changeValue(state: IMap, { name, value }: Field) { 21 | return state.set(name, value) 22 | } 23 | 24 | @Action('reset') 25 | reset(state: IMap) { 26 | return state.merge(fromJS(init)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /__tests__/test-dql.ts: -------------------------------------------------------------------------------- 1 | import { DQL } from '../src/dql' 2 | import { QL, QueryLang } from '../src/ql'; 3 | 4 | 5 | const oneDQL = DQL('oneDQL', [ 6 | '$id', 7 | (id) => id 8 | ]) 9 | 10 | const testDQL = DQL('testDQL', [ 11 | '$id', 12 | oneDQL, 13 | ['user', '$id'], 14 | (user) => user 15 | ]); 16 | 17 | 18 | describe('dynamic query lang test suite', () => { 19 | it('it should be ok', () => { 20 | const lang = testDQL.context({ id: 1 }).analyserLang(testDQL.lang()) 21 | const ql = new QueryLang('testQL', lang); 22 | expect(1).toEqual(ql.lang()[0]); 23 | expect(1).toEqual((ql.lang()[1]).lang()[0]); 24 | expect(['user', 1]).toEqual(ql.lang()[2]); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/timer/src/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import TimerActor from './actor/timer-actor' 3 | 4 | export default class AppStore extends Store { 5 | timer: any; 6 | 7 | bindActor() { 8 | return [ 9 | new TimerActor 10 | ] 11 | } 12 | 13 | constructor(props: IOptions) { 14 | super(props) 15 | //debug,you can quickly test in chrome 16 | if (__DEV__) { 17 | window['store'] = this 18 | } 19 | } 20 | 21 | start = () => { 22 | this.timer = setInterval(() => { 23 | this.dispatch('start') 24 | }, 1000); 25 | }; 26 | 27 | reset = () => { 28 | clearTimeout(this.timer); 29 | this.dispatch('reset') 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /examples/blog/apps/list/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import ListActor from './actor/list-actor' 3 | import { fromJS } from 'immutable' 4 | 5 | export default class AppStore extends Store { 6 | constructor(props: IOptions) { 7 | super(props) 8 | if (__DEV__) { 9 | window['_store'] = this 10 | } 11 | } 12 | 13 | bindActor() { 14 | return [ 15 | new ListActor 16 | ] 17 | } 18 | 19 | 20 | init = () => { 21 | const blogIds = JSON.parse(localStorage.getItem('blog@all') || '[]') 22 | const blogs = blogIds.map(v => JSON.parse( 23 | localStorage.getItem(`blog@${v}`) || '' 24 | )) 25 | this.dispatch('init', fromJS(blogs)) 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /examples/timer/__tests__/time.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as renderer from 'react-test-renderer' 3 | import { StoreProvider } from 'iflux2' 4 | import Timer from '../src/component/timer' 5 | import Store from '../src/store' 6 | 7 | @StoreProvider(Store) 8 | class TimerApp extends React.Component<any, any> { 9 | render() { 10 | return <Timer /> 11 | } 12 | } 13 | 14 | describe('Relax Counter', () => { 15 | it('test counter', () => { 16 | const component = renderer.create(<TimerApp />); 17 | let tree = component.toJSON(); 18 | expect(tree).toMatchSnapshot(); 19 | 20 | window['store'].dispatch('start'); 21 | tree = component.toJSON(); 22 | expect(tree).toMatchSnapshot(); 23 | }) 24 | }) -------------------------------------------------------------------------------- /examples/blog/apps/edit/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import BlogActor from './actor/blog-actor' 3 | 4 | export default class AppStore extends Store { 5 | bindActor() { 6 | return [ 7 | new BlogActor 8 | ] 9 | } 10 | 11 | constructor(props: IOptions) { 12 | super(props) 13 | if (__DEV__) { 14 | window['_store'] = this 15 | } 16 | } 17 | 18 | //;;;;;;;;;;;;;;;;;;action;;;;;;;;;;;;;;;;;;;; 19 | changeTitle = (title: string) => { 20 | this.dispatch('changeTitle', title) 21 | }; 22 | 23 | 24 | changeContent = (content: string) => { 25 | this.dispatch('changeContent', content) 26 | }; 27 | 28 | 29 | submit = () => { 30 | this.dispatch('submit') 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/validator/webpack.config.js: -------------------------------------------------------------------------------- 1 | var HtmlWebpackPlugin = require('html-webpack-plugin') 2 | 3 | module.exports = { 4 | devtool: 'cheap-module-eval-source-map', 5 | 6 | entry: { 7 | index: './src/index.tsx' 8 | }, 9 | output: { 10 | path: './build', 11 | filename: 'bundle-[name].js' 12 | }, 13 | resolve: { 14 | extensions: ['.web.js', '.js', '.json', '.ts', '.tsx'], 15 | }, 16 | module: { 17 | loaders: [ 18 | { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader' }, 19 | { test: /\.css$/, loader: "style-loader!css-loader" } 20 | ] 21 | }, 22 | plugins: [ 23 | new HtmlWebpackPlugin({ 24 | filename: 'index.html', 25 | template: './index.html' 26 | }) 27 | ] 28 | }; 29 | -------------------------------------------------------------------------------- /examples/blog/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /examples/counter/__tests__/counter.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as renderer from 'react-test-renderer' 3 | import Counter from '../src/component/counter' 4 | import Store from '../src/store' 5 | import { StoreProvider } from 'iflux2' 6 | 7 | @StoreProvider(Store) 8 | class App extends Counter { 9 | render() { 10 | return super.render(); 11 | } 12 | } 13 | 14 | describe('Relax Counter', () => { 15 | it('test counter', () => { 16 | const component = renderer.create(<App />); 17 | let tree = component.toJSON(); 18 | expect(tree).toMatchSnapshot(); 19 | 20 | // tree.props.increment(); 21 | window['store'].increment(); 22 | tree = component.toJSON(); 23 | expect(tree).toMatchSnapshot(); 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /examples/timer/__tests__/timer-actor.test.ts: -------------------------------------------------------------------------------- 1 | import TimerActor from '../src/actor/timer-actor' 2 | import { fromJS } from 'immutable' 3 | 4 | describe('test timer actor', () => { 5 | 6 | it('test default value', () => { 7 | const timer = new TimerActor 8 | expect(timer.defaultState()).toEqual({ time: 0 }) 9 | }); 10 | 11 | it('test start', () => { 12 | const timer = new TimerActor 13 | const state = fromJS({ 14 | time: 0 15 | }); 16 | expect(timer.increment(state).toJS()).toEqual({ time: 1 }) 17 | }) 18 | 19 | it('test decrement', () => { 20 | const timer = new TimerActor 21 | const state = fromJS({ 22 | time: 10 23 | }); 24 | expect(timer.reset(state).toJS()).toEqual({ time: 0 }) 25 | }) 26 | 27 | }) -------------------------------------------------------------------------------- /__tests__/test-merge.ts: -------------------------------------------------------------------------------- 1 | import {fromJS, OrderedMap} from 'immutable'; 2 | 3 | function merge(a) { 4 | return OrderedMap().update((value) => { 5 | return a.valueSeq().reduce((init, state) => { 6 | return init.merge(state); 7 | }, value); 8 | }); 9 | } 10 | 11 | test('immutable merge', () => { 12 | var a = fromJS({ 13 | a: {a: {a: 1}}, 14 | b: {b: {b: 1}} 15 | }); 16 | var b = merge(a); 17 | 18 | expect(true).toEqual(b.get('a') == a.getIn(['a', 'a'])); 19 | expect(true).toEqual(b.get('b') == a.getIn(['b', 'b'])) 20 | 21 | const test = a.setIn(['b', 'b', 'b'], 2); 22 | 23 | var c = merge(test); 24 | 25 | expect(true).toEqual(b.get('a') == c.getIn(['a'])); 26 | expect(false).toEqual(b.get('b') == c.getIn(['b'])); 27 | }); -------------------------------------------------------------------------------- /examples/AwesomeApp/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:1.3.1' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | mavenLocal() 18 | jcenter() 19 | maven { 20 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 21 | url "$rootDir/../node_modules/react-native/android" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/iflux2Native/js/home/component/title.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | import React, { Component } from 'react' 3 | import { View, Text, StyleSheet } from 'react-native' 4 | import { Relax } from 'iflux2' 5 | 6 | @Relax 7 | export default class Title extends Component { 8 | static defaultProps = { 9 | text: '' 10 | }; 11 | 12 | render() { 13 | const { loading, text } = this.props 14 | 15 | return ( 16 | <View style={styles.container}> 17 | <Text style={styles.text}>{ text }</Text> 18 | </View> 19 | ) 20 | } 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | container: { 25 | flex: 1, 26 | alignItems: 'center', 27 | justifyContent: 'center' 28 | }, 29 | text: { 30 | fontSize: 14, 31 | fontWeight: 'bold' 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /examples/counter/__tests__/counter-actor.test.ts: -------------------------------------------------------------------------------- 1 | import CounterActor from '../src/actor/counter-actor' 2 | import { fromJS } from 'immutable' 3 | 4 | describe('test counter actor', () => { 5 | 6 | it('test default value', () => { 7 | const counter = new CounterActor 8 | expect(counter.defaultState()).toEqual({ count: 0 }) 9 | }); 10 | 11 | it('test increment', () => { 12 | const counter = new CounterActor 13 | const state = fromJS({ 14 | count: 0 15 | }); 16 | expect(counter.increment(state).toJS()).toEqual({ count: 1 }) 17 | }) 18 | 19 | it('test decrement', () => { 20 | const counter = new CounterActor 21 | const state = fromJS({ 22 | count: 0 23 | }); 24 | expect(counter.decrement(state).toJS()).toEqual({ count: -1 }) 25 | }) 26 | 27 | }) 28 | -------------------------------------------------------------------------------- /examples/todo/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as ReactDOM from 'react-dom' 3 | 4 | import { StoreProvider } from 'iflux2' 5 | import AppStore from './store' 6 | 7 | import Header from './component/header' 8 | import Main from './component/main-section' 9 | import Footer from './component/footer' 10 | 11 | import './css/base.css' 12 | import './css/index.css' 13 | 14 | //debug: true, it will show good logs 15 | @StoreProvider(AppStore, { debug: true }) 16 | export default class TodoApp extends React.Component<any, any> { 17 | render() { 18 | return ( 19 | <section className="todoapp"> 20 | <Header /> 21 | <Main /> 22 | <Footer /> 23 | </section> 24 | ); 25 | } 26 | } 27 | 28 | ReactDOM.render(<TodoApp />, document.getElementById('app')) 29 | -------------------------------------------------------------------------------- /examples/iflux2Native/js/home/store.js: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | 'use strict;' 4 | 5 | import { Store } from 'iflux2' 6 | 7 | import TextActor from './actor/text-actor' 8 | import LoadingActor from './actor/loading-actor' 9 | 10 | import type {StoreOptions } from 'iflux2' 11 | 12 | export default class AppStore extends Store { 13 | constructor(props: StoreOptions) { 14 | super(props) 15 | //dev mode 16 | if (__DEV__) { 17 | window._store = this 18 | } 19 | } 20 | 21 | bindActor() { 22 | return [ 23 | new TextActor, 24 | new LoadingActor 25 | ] 26 | } 27 | 28 | //;;;;;;;;;;;;;;action;;;;;;;;;;;; 29 | loadingSuccess = () => { 30 | this.dispatch('loading:success') 31 | }; 32 | 33 | change = () => { 34 | this.dispatch('change', 'hello world!') 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/blog/apps/detail/store.ts: -------------------------------------------------------------------------------- 1 | //@flow 2 | import { Store, IOptions } from 'iflux2' 3 | import LoadingActor from './actor/loading-actor' 4 | import DetailActor from './actor/detail-actor' 5 | import { fetchDetail } from './webapi' 6 | 7 | export default class AppStore extends Store { 8 | bindActor() { 9 | return [ 10 | new LoadingActor, 11 | new DetailActor 12 | ] 13 | } 14 | 15 | constructor(props: IOptions) { 16 | super(props) 17 | if (__DEV__) { 18 | //debug 19 | window['store'] = this 20 | } 21 | } 22 | 23 | //;;;;;;;;;;;;;;;;;action;;;;;;;;;;;;;;; 24 | init = async (id: number) => { 25 | this.dispatch('loading', true) 26 | const blog = await fetchDetail(id) 27 | this.dispatch('init', blog) 28 | this.dispatch('loading', false) 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /examples/blog/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | module.exports = { 5 | devtool: 'cheap-module-source-map', 6 | entry: './apps/index.tsx', 7 | output: { 8 | path: './build', 9 | filename: 'bundle.js' 10 | }, 11 | resolve: { 12 | extensions: ['.web.js', '.js', '.json', '.ts', '.tsx'], 13 | }, 14 | module: { 15 | loaders: [ 16 | { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader' } 17 | ] 18 | }, 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | __DEV__: true, 22 | }), 23 | new HtmlWebpackPlugin({ 24 | filename: 'index.html', 25 | template: './index.html' 26 | }) 27 | ], 28 | devServer: { 29 | host: '0.0.0.0', 30 | port: 3000 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /src/contrib/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断当前的参数是不是数组 3 | * @param arr 4 | * @returns {boolean} 5 | */ 6 | export function isArray(arr: any): boolean { 7 | return type(arr) === '[object Array]'; 8 | } 9 | 10 | /** 11 | * 是不是函数 12 | * @param fn 13 | * @returns {boolean} 14 | */ 15 | export function isFn(fn: any): boolean { 16 | return type(fn) === '[object Function]'; 17 | } 18 | 19 | /** 20 | * 是不是字符串 21 | * @param str 22 | */ 23 | export function isStr(str: any): boolean { 24 | return type(str) === '[object String]'; 25 | } 26 | 27 | export function isObject(str: any): boolean { 28 | return type(str) === '[object Object]'; 29 | } 30 | 31 | /** 32 | * 判断数据类型 33 | * @param type 34 | * @returns {string} 35 | */ 36 | export function type(type: any): string { 37 | return Object.prototype.toString.call(type); 38 | } 39 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplate/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | node_modules 3 | .idea 4 | coverage 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # node-waf configuration 28 | .lock-wscript 29 | 30 | # Compiled binary addons (http://nodejs.org/api/addons.html) 31 | build/Release 32 | 33 | # Dependency directories 34 | node_modules 35 | jspm_packages 36 | 37 | # Optional npm cache directory 38 | .npm 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | dist 44 | /contrib 45 | build 46 | -------------------------------------------------------------------------------- /examples/counter/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack') 2 | var HtmlWebpackPlugin = require('html-webpack-plugin') 3 | 4 | module.exports = { 5 | devtool: 'cheap-module-eval-source-map', 6 | entry: { 7 | index: './src/index.tsx' 8 | }, 9 | output: { 10 | path: './build', 11 | filename: 'bundle-[name].js' 12 | }, 13 | resolve: { 14 | extensions: ['.web.js', '.js', '.json', '.ts', '.tsx'], 15 | }, 16 | module: { 17 | loaders: [ 18 | { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader' }, 19 | { test: /\.css$/, loader: "style-loader!css-loader" } 20 | ] 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | __DEV__: true, 25 | }), 26 | new HtmlWebpackPlugin({ 27 | filename: 'index.html', 28 | template: './index.html' 29 | }) 30 | ] 31 | }; 32 | -------------------------------------------------------------------------------- /__tests__/__snapshots__/test-react-dql.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`react-dql test suite initial dql 1`] = ` 4 | <div> 5 | <div> 6 | <div> 7 | 1 8 | </div> 9 | <div> 10 | p1 11 | </div> 12 | </div> 13 | <div> 14 | <div> 15 | 2 16 | </div> 17 | <div> 18 | p2 19 | </div> 20 | </div> 21 | <div> 22 | <div> 23 | 3 24 | </div> 25 | <div> 26 | p3 27 | </div> 28 | </div> 29 | <div> 30 | <div> 31 | 4 32 | </div> 33 | <div> 34 | p4 35 | </div> 36 | </div> 37 | <div> 38 | <div> 39 | 5 40 | </div> 41 | <div> 42 | p5 43 | </div> 44 | </div> 45 | <div> 46 | <div> 47 | 6 48 | </div> 49 | <div> 50 | p6 51 | </div> 52 | </div> 53 | </div> 54 | `; 55 | -------------------------------------------------------------------------------- /examples/AwesomeApp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AwesomeApp", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node node_modules/react-native/local-cli/cli.js start", 7 | "test": "jest", 8 | "compile": "node_modules/.bin/tsc", 9 | "watch": "node_modules/.bin/tsc -w", 10 | "postinstall": "npm install ./react-dom" 11 | }, 12 | "dependencies": { 13 | "@types/react": "^15.0.14", 14 | "@types/react-native": "^0.37.13", 15 | "iflux2": "^1.4.4", 16 | "react": "~15.4.1", 17 | "react-native": "0.42.0" 18 | }, 19 | "devDependencies": { 20 | "babel-jest": "19.0.0", 21 | "babel-plugin-module-resolver": "^2.5.0", 22 | "babel-preset-react-native": "1.9.1", 23 | "jest": "19.0.2", 24 | "react-test-renderer": "~15.4.1", 25 | "typescript": "^2.2.1" 26 | }, 27 | "jest": { 28 | "preset": "react-native" 29 | } 30 | } -------------------------------------------------------------------------------- /examples/todo/src/ql.ts: -------------------------------------------------------------------------------- 1 | import { QL, DQL } from 'iflux2' 2 | import { fromJS } from 'immutable' 3 | 4 | /** 5 | * 查询输入框的值 6 | */ 7 | export const valueQL = QL('valueQL', [ 8 | 'value', 9 | value => value 10 | ]) 11 | 12 | /** 13 | * 查询todo 14 | */ 15 | export const todoQL = QL('todoQL', [ 16 | 'todo', 17 | 'filterStatus', 18 | (todo, filterStatus) => { 19 | if (filterStatus === '') { 20 | return todo 21 | } 22 | const done = filterStatus === 'completed' 23 | return todo.filter(v => v.get('done') === done) 24 | } 25 | ]) 26 | 27 | /** 28 | * 查询todo的数量 29 | */ 30 | export const countQL = QL('countQL', [ 31 | todoQL, 32 | todoQL => todoQL.count() 33 | ]) 34 | 35 | /** 36 | * 动态查询,运行期relax会自动替换$index 37 | */ 38 | export const todoDQL = DQL('todoDQL', [ 39 | ['todo', '$index'], 40 | todo => todo || fromJS({}) 41 | ]) -------------------------------------------------------------------------------- /examples/validator/src/components/form-field.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | export default class FormField extends React.Component<any, any> { 4 | props: { 5 | error?: Error; 6 | label?: string; 7 | required?: boolean; 8 | children?: any; 9 | }; 10 | 11 | render() { 12 | const { error, label, required } = this.props 13 | 14 | return ( 15 | <tr> 16 | <td> 17 | <label> 18 | {required 19 | ? <span style={style}>*</span> 20 | : null} 21 | {label} 22 | </label> 23 | </td> 24 | <td> 25 | {this.props.children} 26 | {error 27 | ? <span style={style}>{error}</span> 28 | : null} 29 | </td> 30 | </tr> 31 | ) 32 | } 33 | } 34 | 35 | const style = { 36 | color: 'red' 37 | }; 38 | -------------------------------------------------------------------------------- /examples/validator/src/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import UserActor from './actor/user-actor' 3 | import ValidateFieldActor from './actor/validate-field-actor' 4 | 5 | export default class AppStore extends Store { 6 | constructor(props: IOptions) { 7 | super(props) 8 | window['store'] = this 9 | } 10 | 11 | bindActor() { 12 | return [ 13 | new UserActor, 14 | new ValidateFieldActor 15 | ] 16 | } 17 | 18 | //;;;;;;;;;;;;;;;;handle action;;;;;;;;;;;;;;;;;;;; 19 | changeValue = (name: string, value: string) => { 20 | this.dispatch('changeValue', { name, value }) 21 | this.dispatch('validateField', name) 22 | }; 23 | 24 | validateField = (name: string) => { 25 | this.dispatch('validateField', name) 26 | }; 27 | 28 | reset = () => { 29 | this.dispatch('reset') 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /examples/counter/__tests__/__snapshots__/counter.test.tsx.snap: -------------------------------------------------------------------------------- 1 | exports[`Relax Counter test counter 1`] = ` 2 | <div> 3 | <a 4 | href="javascript:void(0);" 5 | onClick={[Function]}> 6 | increment 7 | </a> 8 | <br /> 9 | <span> 10 | 0 11 | </span> 12 | <br /> 13 | <span> 14 | QL:0 15 | </span> 16 | <br /> 17 | <a 18 | href="javascript:void(0);" 19 | onClick={[Function]}> 20 | decrement 21 | </a> 22 | </div> 23 | `; 24 | 25 | exports[`Relax Counter test counter 2`] = ` 26 | <div> 27 | <a 28 | href="javascript:void(0);" 29 | onClick={[Function]}> 30 | increment 31 | </a> 32 | <br /> 33 | <span> 34 | 1 35 | </span> 36 | <br /> 37 | <span> 38 | QL:1 39 | </span> 40 | <br /> 41 | <a 42 | href="javascript:void(0);" 43 | onClick={[Function]}> 44 | decrement 45 | </a> 46 | </div> 47 | `; 48 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeAppTests/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>$(PRODUCT_NAME)</string> 15 | <key>CFBundlePackageType</key> 16 | <string>BNDL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1</string> 23 | </dict> 24 | </plist> 25 | -------------------------------------------------------------------------------- /examples/timer/src/component/timer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax } from 'iflux2' 3 | const noop = () => { } 4 | 5 | @Relax 6 | export default class Counter extends React.Component<any, any> { 7 | props: { 8 | time?: number; 9 | start?: () => void; 10 | reset?: () => void; 11 | }; 12 | 13 | static defaultProps = { 14 | time: 0, 15 | start: noop, 16 | reset: noop, 17 | }; 18 | 19 | render() { 20 | const { time, start, reset } = this.props 21 | 22 | const style = { 23 | marginLeft: 10, 24 | marginRight: 10, 25 | fontSize: 18, 26 | color: 'red' 27 | } 28 | 29 | return ( 30 | <div> 31 | <a href='javascript:void(0);' onClick={start}>start</a> 32 | <span style={style}>{time}</span> 33 | <a href='javascript:void(0);' onClick={reset}>reset</a> 34 | </div> 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /examples/timer/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var HtmlWebpackPlugin = require('html-webpack-plugin') 4 | 5 | module.exports = { 6 | devtool: 'cheap-module-eval-source-map', 7 | entry: { 8 | index: './src/index.tsx' 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, './build'), 12 | filename: 'bundle-[name].js' 13 | }, 14 | resolve: { 15 | extensions: ['.web.js', '.js', '.json', '.ts', '.tsx'], 16 | }, 17 | module: { 18 | loaders: [ 19 | { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader' }, 20 | { test: /\.css$/, loader: "style-loader!css-loader" } 21 | ] 22 | }, 23 | plugins: [ 24 | new webpack.DefinePlugin({ 25 | __DEV__: true, 26 | }), 27 | new HtmlWebpackPlugin({ 28 | filename: 'index.html', 29 | template: './index.html' 30 | }) 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /examples/todo/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var webpack = require('webpack') 3 | var HtmlWebpackPlugin = require('html-webpack-plugin') 4 | 5 | module.exports = { 6 | devtool: 'cheap-module-eval-source-map', 7 | entry: { 8 | index: './src/index.tsx' 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, './build'), 12 | filename: 'bundle-[name].js' 13 | }, 14 | resolve: { 15 | extensions: ['.web.js', '.js', '.json', '.ts', '.tsx'], 16 | }, 17 | module: { 18 | loaders: [ 19 | { test: /\.tsx?$/, exclude: /node_modules/, loader: 'ts-loader' }, 20 | { test: /\.css$/, loader: "style-loader!css-loader" } 21 | ] 22 | }, 23 | plugins: [ 24 | new webpack.DefinePlugin({ 25 | __DEV__: true 26 | }), 27 | new HtmlWebpackPlugin({ 28 | filename: 'index.html', 29 | template: './index.html' 30 | }) 31 | ] 32 | }; 33 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp-tvOSTests/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>$(PRODUCT_NAME)</string> 15 | <key>CFBundlePackageType</key> 16 | <string>BNDL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1</string> 23 | </dict> 24 | </plist> 25 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplateTests/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>$(PRODUCT_NAME)</string> 15 | <key>CFBundlePackageType</key> 16 | <string>BNDL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1</string> 23 | </dict> 24 | </plist> 25 | -------------------------------------------------------------------------------- /examples/validator/src/actor/validate-field-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | import { OrderedSet, fromJS } from 'immutable' 3 | 4 | export default class ValidateFieldActor extends Actor { 5 | defaultState() { 6 | return { 7 | fields: OrderedSet() 8 | } 9 | } 10 | 11 | @Action('validateField') 12 | validateField(state: IMap, field: string) { 13 | //全部校验 14 | if (field === 'all') { 15 | return state.update('fields', (fields) => fields.merge(fromJS([ 16 | 'username', 17 | 'password', 18 | 'confirm', 19 | 'email', 20 | 'qq' 21 | ]))) 22 | } 23 | 24 | //动态的追加校验 25 | return state.update('fields', (fields) => { 26 | return fields.add(field) 27 | }) 28 | } 29 | 30 | 31 | @Action('reset') 32 | reset(state: IMap) { 33 | return state.update('fields', (fields) => fields.clear()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /documents/gitbook/relax.md: -------------------------------------------------------------------------------- 1 | # Relax 2 | 3 | > Talk is cheap, show me code 4 | 5 | ```js 6 | import React, {Component} from 'react' 7 | import {Relax} from 'iflux2' 8 | 9 | @Relax 10 | class User extends Component { 11 | /** 12 | *relax 会自动查询数据注入 13 | */ 14 | static defaultProps = { 15 | name: nameQL, 16 | url: urlQL 17 | id: 0 18 | }; 19 | 20 | render() { 21 | //自动注入name,url,id 22 | const {id, name, url} = this.props; 23 | 24 | return ( 25 | <div> 26 | id: {id} 27 | name: {name} 28 | url: <img src={url}/> 29 | </div> 30 | ); 31 | } 32 | } 33 | ``` 34 | 35 | Relax是iflux2中非常重要的容器组件,类似Spring的依赖注入一样,会根据子组件的defaultProps中声明的数据,通过智能计算属性的值,然后注入到子组件的内部。 36 | 37 | 计算的属性的顺序: 38 | 1. 属性的值是不是query-lang,如果是通过store的bigQuery计算 39 | 2. 是不是父组件传递的props 40 | 3. 是不是store的方法 41 | 4. 是不是store中的状态值 42 | 5. 默认值 43 | 44 | __store__:store的值,是通过StoreProvider绑定到顶层组件的context中。 45 | -------------------------------------------------------------------------------- /examples/blog/apps/list/component/blog-list.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax, IMap } from 'iflux2' 3 | import { fromJS, List } from 'immutable' 4 | import { Link } from 'react-router' 5 | 6 | @Relax 7 | export default class BlogList extends React.Component<any, any> { 8 | props: { 9 | blogs?: List<IMap> 10 | } 11 | static defaultProps = { 12 | blogs: fromJS([]) 13 | }; 14 | 15 | render() { 16 | const { blogs } = this.props 17 | 18 | if (blogs.isEmpty()) { 19 | return ( 20 | <div> 21 | 还木有blog 22 | <Link to="/new" > 赶紧的....</Link> 23 | </div> 24 | ) 25 | } 26 | 27 | return ( 28 | <ul> 29 | {blogs.map((v, k) => ( 30 | <li key={k} > 31 | <Link key={k} to={`/detail/${v.get('id')}`}> 32 | {v.get('title')} - {v.get('createAt')} 33 | </Link> 34 | </li> 35 | ))} 36 | </ul> 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /src/actor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Actor,致敬Erlang,Scala的akka的Actor model 3 | * Actor, 独立计算的执行单元 4 | * 我们不共享状态(share state), 只去transform state 5 | */ 6 | import { OrderedMap } from 'immutable' 7 | 8 | type IMap = OrderedMap<string, any>; 9 | type Route = { 10 | [name: string]: (state: IMap, params?: any) => IMap 11 | }; 12 | 13 | export default class Actor { 14 | //记录当前的路由信息 15 | _route: Route; 16 | 17 | /** 18 | * 定义actor的默认状态 19 | * @returns {{}} 20 | */ 21 | defaultState(): Object { 22 | return {}; 23 | } 24 | 25 | /** 26 | * actor的mode的receive,被store在dispatch的时候调用 27 | * @param msg 28 | * @param state 29 | * @param param 30 | * @returns {Object} 31 | */ 32 | receive(msg: string, state: IMap, param?: any): IMap { 33 | //this._route是在@Action标记中初始化完成 34 | const route = this._route || {}; 35 | //获取处理的函数 36 | const fn = route[msg]; 37 | 38 | //如果可以处理返回处理后的结果,否则直接返回state 39 | return fn ? fn.call(this, state, param) : state; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.useDeprecatedNdk=true 21 | -------------------------------------------------------------------------------- /examples/counter/src/component/counter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax } from 'iflux2' 3 | import { countQL } from '../ql' 4 | const noop = () => { } 5 | 6 | type Handler = () => {}; 7 | 8 | @Relax 9 | export default class Counter extends React.Component<any, any> { 10 | props: { 11 | count?: number; 12 | countQL?: string; 13 | increment?: Handler; 14 | decrement?: Handler; 15 | }; 16 | 17 | static defaultProps = { 18 | count: 0, 19 | countQL, 20 | increment: noop, 21 | decrement: noop 22 | }; 23 | 24 | render() { 25 | const { count, countQL, increment, decrement } = this.props 26 | 27 | return ( 28 | <div> 29 | <a href='javascript:void(0);' onClick={increment}>increment</a> 30 | <br /> 31 | <span>{count}</span> 32 | <br /> 33 | <span>{countQL}</span> 34 | <br /> 35 | <a href='javascript:void(0);' onClick={decrement}>decrement</a> 36 | </div> 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/AwesomeApp/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 50 | 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots 54 | /js 55 | -------------------------------------------------------------------------------- /examples/blog/apps/edit/actor/blog-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | 3 | export default class BlogActor extends Actor { 4 | defaultState() { 5 | return { 6 | id: 0, 7 | title: '', 8 | content: '', 9 | createAt: '' 10 | } 11 | } 12 | 13 | @Action('changeTitle') 14 | changeTitle(state: IMap, title: string) { 15 | return state.set('title', title) 16 | } 17 | 18 | @Action('changeContent') 19 | changeContent(state: IMap, content: string) { 20 | return state.set('content', content) 21 | } 22 | 23 | @Action('submit') 24 | submit(state: IMap) { 25 | const id = Date.now() 26 | const blog = ( 27 | state 28 | .set('id', id) 29 | .set('createAt', new Date()) 30 | ) 31 | 32 | const blogIds = JSON.parse(localStorage.getItem('blog@all') || '[]').concat([id]) 33 | localStorage.setItem(`blog@all`, JSON.stringify(blogIds)) 34 | localStorage.setItem(`blog@${id}`, JSON.stringify(blog)) 35 | 36 | window.location.href = '/#' 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/timer/__tests__/__snapshots__/time.test.tsx.snap: -------------------------------------------------------------------------------- 1 | exports[`Relax Counter test counter 1`] = ` 2 | <div> 3 | <a 4 | href="javascript:void(0);" 5 | onClick={[Function]}> 6 | start 7 | </a> 8 | <span 9 | style={ 10 | Object { 11 | "color": "red", 12 | "fontSize": 18, 13 | "marginLeft": 10, 14 | "marginRight": 10, 15 | } 16 | }> 17 | 0 18 | </span> 19 | <a 20 | href="javascript:void(0);" 21 | onClick={[Function]}> 22 | reset 23 | </a> 24 | </div> 25 | `; 26 | 27 | exports[`Relax Counter test counter 2`] = ` 28 | <div> 29 | <a 30 | href="javascript:void(0);" 31 | onClick={[Function]}> 32 | start 33 | </a> 34 | <span 35 | style={ 36 | Object { 37 | "color": "red", 38 | "fontSize": 18, 39 | "marginLeft": 10, 40 | "marginRight": 10, 41 | } 42 | }> 43 | 1 44 | </span> 45 | <a 46 | href="javascript:void(0);" 47 | onClick={[Function]}> 48 | reset 49 | </a> 50 | </div> 51 | `; 52 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/java/com/iflux2nativetemplate/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.iflux2nativetemplate; 2 | 3 | import android.app.Application; 4 | import android.util.Log; 5 | 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactInstanceManager; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.react.shell.MainReactPackage; 11 | 12 | import java.util.Arrays; 13 | import java.util.List; 14 | 15 | public class MainApplication extends Application implements ReactApplication { 16 | 17 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 18 | @Override 19 | protected boolean getUseDeveloperSupport() { 20 | return BuildConfig.DEBUG; 21 | } 22 | 23 | @Override 24 | protected List<ReactPackage> getPackages() { 25 | return Arrays.<ReactPackage>asList( 26 | new MainReactPackage() 27 | ); 28 | } 29 | }; 30 | 31 | @Override 32 | public ReactNativeHost getReactNativeHost() { 33 | return mReactNativeHost; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/AwesomeApp/src/apps/hello/component/hello.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { View, Text, StyleSheet } from 'react-native' 3 | import { Relax } from "iflux2" 4 | import { noop } from "uikit"; 5 | 6 | 7 | type Handler = () => void; 8 | 9 | @Relax 10 | export default class Hello extends React.Component<any, any> { 11 | props: { 12 | text?: string; 13 | count?: number; 14 | like?: Handler; 15 | }; 16 | 17 | static defaultProps = { 18 | text: '', 19 | count: 1, 20 | like: noop, 21 | }; 22 | 23 | render() { 24 | const { text, count, like } = this.props 25 | 26 | return ( 27 | <View style={styles.container}> 28 | <Text 29 | style={styles.text} 30 | onPress={like}> 31 | {text}{`+${count}`} 32 | </Text> 33 | </View> 34 | ) 35 | } 36 | } 37 | 38 | const styles = StyleSheet.create({ 39 | container: { 40 | flex: 1, 41 | justifyContent: 'center', 42 | alignItems: 'center', 43 | } as React.ViewStyle, 44 | text: { 45 | fontSize: 16, 46 | fontWeight: 'bold', 47 | color: 'blue' 48 | } as React.TextStyle 49 | }) -------------------------------------------------------------------------------- /examples/todo/src/component/header.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax } from 'iflux2' 3 | import { valueQL } from '../ql' 4 | 5 | const noop = () => { }; 6 | 7 | @Relax 8 | export default class Header extends React.Component<any, any> { 9 | props: { 10 | value?: string; 11 | submit?: () => void; 12 | changeValue?: (text: string) => void; 13 | }; 14 | 15 | static defaultProps = { 16 | value: valueQL, 17 | submit: noop, 18 | changeValue: noop, 19 | }; 20 | 21 | render() { 22 | return ( 23 | <header className="header"> 24 | <h1>todos</h1> 25 | <input value={this.props.value} 26 | className="new-todo" 27 | onKeyDown={this._handleKeyDown} 28 | onChange={this._handleChange} 29 | placeholder="What needs to be done?" 30 | autoFocus /> 31 | </header> 32 | ); 33 | } 34 | 35 | _handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { 36 | this.props.changeValue(e.target.value); 37 | }; 38 | 39 | 40 | _handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { 41 | if (e.keyCode === 13) { 42 | this.props.submit(); 43 | } 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /documents/README.md: -------------------------------------------------------------------------------- 1 | ## Get started 2 | 3 | ```sh 4 | #安装node, 安装成功 5 | → node -v [1bae924] 6 | v6.3.0 7 | 8 | #安装gitbook-cli 9 | npm install gitbook-cli -g 10 | 11 | #安装成功 12 | → gitbook -V [1bae924] 13 | CLI version: 2.3.0 14 | GitBook version: 3.1.1 15 | 16 | #电子书 17 | cd iflux2/documents/gitbook 18 | 19 | → gitbook serve [1bae924] 20 | Live reload server started on port: 35729 21 | Press CTRL+C to quit ... 22 | 23 | info: 7 plugins are installed 24 | info: loading plugin "livereload"... OK 25 | info: loading plugin "highlight"... OK 26 | info: loading plugin "search"... OK 27 | info: loading plugin "lunr"... OK 28 | info: loading plugin "sharing"... OK 29 | info: loading plugin "fontsettings"... OK 30 | info: loading plugin "theme-default"... OK 31 | info: found 11 pages 32 | info: found 1 asset files 33 | info: >> generation finished with success in 1.8s ! 34 | 35 | Starting server ... 36 | Serving book on http://localhost:4000 37 | 38 | #浏览器打开就可以访问了:) 39 | ``` 40 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/java/com/awesomeapp/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.awesomeapp; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.react.ReactApplication; 6 | import com.facebook.react.ReactNativeHost; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.shell.MainReactPackage; 9 | import com.facebook.soloader.SoLoader; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class MainApplication extends Application implements ReactApplication { 15 | 16 | private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { 17 | @Override 18 | public boolean getUseDeveloperSupport() { 19 | return BuildConfig.DEBUG; 20 | } 21 | 22 | @Override 23 | protected List<ReactPackage> getPackages() { 24 | return Arrays.<ReactPackage>asList( 25 | new MainReactPackage() 26 | ); 27 | } 28 | }; 29 | 30 | @Override 31 | public ReactNativeHost getReactNativeHost() { 32 | return mReactNativeHost; 33 | } 34 | 35 | @Override 36 | public void onCreate() { 37 | super.onCreate(); 38 | SoLoader.init(this, /* native exopackage */ false); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /documents/gitbook/README.md: -------------------------------------------------------------------------------- 1 | > iflux2 = React + immutable + Reactive 2 | 3 | 4 | ### iflux2, new idea, new world! 5 | 6 | 技术也是时尚驱动的,我们常常臣服于时尚,面对快速的变化常常让我们局促不安,开始焦虑,唯恐错过了些什么。怎么打破这种焦虑?需要在快速变化得世界里保持清醒,保持独立的思考和认知。 7 | 8 | 因为你今天觉得redux很酷,明天觉得mobx帅炸,后天没有swift活不下去。无论是酷,还是帅炸不能很好的解决我们的痛点,不能做出更好的产品都是[然并卵]。 9 | 10 | 我们需要花费更多的时间去思考理解业务中的难点,痛点,通过技术的创新来不断的解决业务中的问题,make something people need. 11 | 12 | 遥想React@0.11的年代,那年[他]还很羞涩,/*@JSX.DOM*/的写法惊呆了小伙伴, 但是组件化的思想照亮我们前进的方向。一路相伴,我们见证了一个个产品的上线。我们相互鉴证着彼此的成长。蓦然回首,恍如隔世。 13 | 14 | ### 真正牛*的技术,都是静悄悄的跑在线上... 15 | 16 | React本身是一个抽象非常好,api简洁优雅的专注View层的Library。但是对于一个应用来说,不仅仅是一个view层,数据是活的,交互式活的,So考验React的项目并不仅仅是React的本身,而是怎么设计一个匹配React的状态管理容器。 React@0.11的年代可以参考的东西还比较少,fb官方给出flux的参考实现,这个仅仅是一个参考,我们不断的分析思考,觉得模板代码太多,写起来不够直接, 主要因为: 17 | 18 | 1. 拆分了过多的Store,导致数据同步的成本升高 19 | 2. 大量的事件监听和触发,导致大量的模板方法 20 | 3. React的数据源有两种props和state,我们认为props更具通用性,更是简单可预测,而flux选择了state,所有带来大量的数据监听,我们认为以数据透传代替事件透传更棒(怎么解决数据层层的透传的verbose的味道)。 21 | 22 | 在那样一个时间点,阴差阳错的在接触Clojure,开启了函数式编程的大门,进入一个崭新的世界, 尤其对数据的态度,这是非常重要的一点,从可变的状态到不可变的值是一个质的飞跃。 23 | 24 | 我们需要在js中的不可变数据结构,一个clojurescript实现的js的不可变数据的库mori, 这个库非常的棒,但是语法非常偏向Clojure的lisp系的语法使用方式,让我们小伙伴稍难接受,我们不断的追寻最终了发现了immutable.js如获至宝。 25 | 26 | 从此踏上,Immutable UI的前端路. 27 | 28 | 在实践中成长,在问题中求生存。 29 | -------------------------------------------------------------------------------- /examples/todo/src/store.ts: -------------------------------------------------------------------------------- 1 | import { Store, IOptions } from 'iflux2' 2 | import TextActor from './actor/text-actor' 3 | import TodoActor from './actor/todo-actor' 4 | import FilterActor from './actor/filter-actor' 5 | 6 | export default class AppStore extends Store { 7 | bindActor() { 8 | return [ 9 | new TextActor, 10 | new TodoActor, 11 | new FilterActor 12 | ]; 13 | } 14 | 15 | 16 | constructor(props: IOptions) { 17 | super(props) 18 | if (__DEV__) { 19 | window['_store'] = this 20 | } 21 | } 22 | 23 | changeValue = (value: string) => { 24 | this.dispatch('changeValue', value); 25 | }; 26 | 27 | submit = () => { 28 | const value = this.state().get('value'); 29 | this.dispatch('submit', value); 30 | }; 31 | 32 | changeFilter = (status: string) => { 33 | this.dispatch('filter', status) 34 | }; 35 | 36 | toggle = (index: number) => { 37 | this.dispatch('toggle', index) 38 | } 39 | 40 | destroy = (index: number) => { 41 | this.dispatch('destroy', index) 42 | }; 43 | 44 | toggleAll = (checked: number) => { 45 | this.dispatch('toggleAll', checked) 46 | }; 47 | 48 | clearCompleted = () => { 49 | this.dispatch('clearCompleted') 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /examples/blog/apps/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { render } from 'react-dom' 3 | import { 4 | Router, 5 | Route, 6 | IndexRoute, 7 | Link, 8 | hashHistory 9 | } from 'react-router' 10 | 11 | declare const System: any; 12 | 13 | class Blog extends React.Component<any, any> { 14 | render() { 15 | return ( 16 | <div> 17 | <h3> 18 | <Link to="/new">new post...</Link> 19 | </h3> 20 | {this.props.children} 21 | </div> 22 | ) 23 | } 24 | } 25 | 26 | render(( 27 | <Router history={hashHistory}> 28 | <Router path="/" component={Blog}> 29 | <IndexRoute getComponent={(location, cb) => { 30 | System 31 | .import('./list') 32 | .then(List => cb(null, List.default)) 33 | }} /> 34 | 35 | <Route path="new" getComponent={(location, cb) => { 36 | System 37 | .import('./edit') 38 | .then(Edit => cb(null, Edit.default)) 39 | }} /> 40 | 41 | <Route path="detail/:id" getComponent={(location, cb) => { 42 | System 43 | .import('./detail') 44 | .then(Detail => cb(null, Detail.default)) 45 | }} /> 46 | </Router> 47 | </Router> 48 | ), document.getElementById('app')) 49 | -------------------------------------------------------------------------------- /documents/gitbook/store-provider.md: -------------------------------------------------------------------------------- 1 | # StoreProvider 2 | 3 | > talk is cheap, show me code 4 | 5 | ```js 6 | import React, {Component} from 'react'; 7 | import {StoreProvider} from 'iflux2' 8 | import AppStore from './store' 9 | 10 | 11 | @StoreProvider(AppStore) 12 | class ShoppingCart extends Component { 13 | render() { 14 | return ( 15 | <Scene> 16 | <HeaderContainer/> 17 | <ShoppingListContainer/> 18 | <BottomToolBarContainer/> 19 | </Scene> 20 | ) 21 | } 22 | } 23 | ``` 24 | 25 | StoreProvider容器组件衔接我们的React组件和AppStore。向React组件提供数据源。 26 | 27 | 在StoreProvider中的主要任务是, 28 | 1. 初始化我们的AppStore 29 | 2. 将AppStore的对象绑定到React组件的上下文 30 | 3. Relay就是通过上下文取的store对象 31 | 32 | 我们还提供了debug模式,开启debug模式 33 | ```js 34 | import React, {Component} from 'react'; 35 | import {StoreProvider} from 'iflux2' 36 | import AppStore from './store' 37 | 38 | 39 | //enable debug 40 | @StoreProvider(AppStore, {debug: true}) 41 | class ShoppingCart extends Component { 42 | render() { 43 | return ( 44 | <Scene> 45 | <HeaderContainer/> 46 | <ShoppingListContainer/> 47 | <BottomToolBarContainer/> 48 | </Scene> 49 | ) 50 | } 51 | } 52 | ``` 53 | 一个简单的debug标记,我们就可以跟踪每个dispatch,每个Relax,每个bigQuery 54 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="com.iflux2nativetemplate" 3 | android:versionCode="1" 4 | android:versionName="1.0"> 5 | 6 | <uses-permission android:name="android.permission.INTERNET" /> 7 | <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 8 | 9 | <uses-sdk 10 | android:minSdkVersion="16" 11 | android:targetSdkVersion="22" /> 12 | 13 | <application 14 | android:name=".MainApplication" 15 | android:allowBackup="true" 16 | android:label="@string/app_name" 17 | android:icon="@mipmap/ic_launcher" 18 | android:theme="@style/AppTheme"> 19 | <activity 20 | android:name=".MainActivity" 21 | android:label="@string/app_name" 22 | android:configChanges="keyboard|keyboardHidden|orientation|screenSize"> 23 | <intent-filter> 24 | <action android:name="android.intent.action.MAIN" /> 25 | <category android:name="android.intent.category.LAUNCHER" /> 26 | </intent-filter> 27 | </activity> 28 | <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> 29 | </application> 30 | 31 | </manifest> 32 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | <manifest xmlns:android="http://schemas.android.com/apk/res/android" 2 | package="com.awesomeapp" 3 | android:versionCode="1" 4 | android:versionName="1.0"> 5 | 6 | <uses-permission android:name="android.permission.INTERNET" /> 7 | <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> 8 | 9 | <uses-sdk 10 | android:minSdkVersion="16" 11 | android:targetSdkVersion="22" /> 12 | 13 | <application 14 | android:name=".MainApplication" 15 | android:allowBackup="true" 16 | android:label="@string/app_name" 17 | android:icon="@mipmap/ic_launcher" 18 | android:theme="@style/AppTheme"> 19 | <activity 20 | android:name=".MainActivity" 21 | android:label="@string/app_name" 22 | android:configChanges="keyboard|keyboardHidden|orientation|screenSize" 23 | android:windowSoftInputMode="adjustResize"> 24 | <intent-filter> 25 | <action android:name="android.intent.action.MAIN" /> 26 | <category android:name="android.intent.category.LAUNCHER" /> 27 | </intent-filter> 28 | </activity> 29 | <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" /> 30 | </application> 31 | 32 | </manifest> 33 | -------------------------------------------------------------------------------- /examples/iflux2Native/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IJ 26 | # 27 | *.iml 28 | .idea 29 | .gradle 30 | local.properties 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | 37 | # BUCK 38 | buck-out/ 39 | \.buckd/ 40 | android/app/libs 41 | # Logs 42 | logs 43 | *.log 44 | npm-debug.log* 45 | 46 | # Runtime data 47 | pids 48 | *.pid 49 | *.seed 50 | 51 | # Directory for instrumented libs generated by jscoverage/JSCover 52 | lib-cov 53 | 54 | # Coverage directory used by tools like istanbul 55 | coverage 56 | 57 | # nyc test coverage 58 | .nyc_output 59 | 60 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 61 | .grunt 62 | 63 | # node-waf configuration 64 | .lock-wscript 65 | 66 | # Compiled binary addons (http://nodejs.org/api/addons.html) 67 | build/Release 68 | 69 | # Dependency directories 70 | node_modules 71 | jspm_packages 72 | 73 | # Optional npm cache directory 74 | .npm 75 | 76 | # Optional REPL history 77 | .node_repl_history 78 | -------------------------------------------------------------------------------- /src/decorator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Action decorator 3 | * 4 | * 用于标记Actor中的实力方法,主要的作用是给Actor绑定当前的handler方法 5 | * 便于Actor的receive方法可以搜索到哪个handler可以处理dispatch过来的事件 6 | * 7 | * Usage: 8 | * import {Actor, Action} from 'iflux2' 9 | * 10 | * class HelloActor extends Actor { 11 | * defaultState() { 12 | * return {text: 'hello iflxu2'} 13 | * } 14 | * 15 | * @Action('change') 16 | * change(state, text) { 17 | * return state.set('text', text); 18 | * } 19 | * 20 | * } 21 | * 22 | * 23 | * @param msg 事件名称 24 | * @constructor 25 | */ 26 | export const Action = (msg: string) => ( 27 | target: any, 28 | props: any, 29 | descriptor: TypedPropertyDescriptor<any> 30 | ) => { 31 | target._route = target._route || {}; 32 | target._route[msg] = descriptor.value; 33 | }; 34 | 35 | 36 | /** 37 | * 动态的绑定组件的上下文 38 | * 39 | * Usage 40 | * import React, {Component} from 'iflux2' 41 | * 42 | * @CtxStoreName('_store') 43 | * class Hello extends Component { 44 | * render() { 45 | * return <div>hello world</div> 46 | * } 47 | * } 48 | * @param obj 绑定上下文 49 | * @returns {function(Object)} 50 | */ 51 | export const CtxStoreName = (name: string) => { 52 | return (target: Object) => { 53 | //动态的在组件的上下午绑定storeName 54 | //通过该标记反向的告诉Relax获取正取的上下文store 55 | target['_ctxStoreName'] = name; 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /examples/AwesomeApp/index.android.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * @flow 5 | */ 6 | 7 | import React, { Component } from 'react'; 8 | import { 9 | AppRegistry, 10 | StyleSheet, 11 | Text, 12 | View 13 | } from 'react-native'; 14 | 15 | export default class AwesomeApp extends Component { 16 | render() { 17 | return ( 18 | <View style={styles.container}> 19 | <Text style={styles.welcome}> 20 | Welcome to React Native! 21 | </Text> 22 | <Text style={styles.instructions}> 23 | To get started, edit index.android.js 24 | </Text> 25 | <Text style={styles.instructions}> 26 | Double tap R on your keyboard to reload,{'\n'} 27 | Shake or press menu button for dev menu 28 | </Text> 29 | </View> 30 | ); 31 | } 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | container: { 36 | flex: 1, 37 | justifyContent: 'center', 38 | alignItems: 'center', 39 | backgroundColor: '#F5FCFF', 40 | }, 41 | welcome: { 42 | fontSize: 20, 43 | textAlign: 'center', 44 | margin: 10, 45 | }, 46 | instructions: { 47 | textAlign: 'center', 48 | color: '#333333', 49 | marginBottom: 5, 50 | }, 51 | }); 52 | 53 | AppRegistry.registerComponent('AwesomeApp', () => AwesomeApp); 54 | -------------------------------------------------------------------------------- /documents/gitbook/actor.md: -------------------------------------------------------------------------------- 1 | # Actor 2 | 学习Erlang或者Scala中的Actor的计算模型,一个独立的计算单元,主要作用就是转换我们的状态数据,以OO的观点看数据,更多的是hide data,以fp的观点看数据,更多的是transform data.我们取OO得形,得FP的意。以OO的方式封装我们代码的结构,以函数式的方式处理状态,为什么我们可以这样放心大胆的用,感谢Immutable。 3 | 4 | 5 | # Talk is cheap, Show me code 6 | 7 | ```js 8 | //简单的例子 9 | import {Actor, Action} from 'iflux2' 10 | 11 | export default class Iflux2Actor extends Actor { 12 | /** 13 | * 领域模型数据 14 | */ 15 | defaultState() { 16 | //会被自动转化为immutable的状态 17 | return { 18 | id: 1, 19 | name: 'iflux2', 20 | version: '1.0.0' 21 | } 22 | } 23 | 24 | /** 25 | * 注册dispatch的自定义handler 26 | */ 27 | @Action('update:version') 28 | update(state) { 29 | return state.set('version', '1.1.0'); 30 | } 31 | } 32 | ``` 33 | 34 | 35 | > 简单我们我们永恒的追求 36 | 37 | Actor所有子Actor的父类,子Actor继承Actor这个父类之后,通过简单的@Action这个Decorator 38 | 就获取了receive动态分派的能力,store在dispatch方法内部会自动的调用receive 39 | 40 | 41 | # 关于decorator 42 | 借助Babel的decorators插件 43 | ```sh 44 | # npm install babel-plugin-transform-decorators-legacy --save-dev 45 | # or 46 | # yarn add babel-plugin-transform-decorators-legacy --dev 47 | ``` 48 | 49 | 配置.babelrc 50 | 51 | ```js 52 | { 53 | “plugins”: [ 54 | "transform-decorators-legacy" 55 | ] 56 | } 57 | 58 | ``` 59 | 我们就可以使用这个@Action 60 | 61 | 62 | # 等到webworker 63 | 后面会让Actor变成名副其实的actor,支持并行计算 64 | -------------------------------------------------------------------------------- /src/ql.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 查询语言 3 | * @flow 4 | */ 5 | 6 | 'use strict'; 7 | 8 | import { isQuery } from './util'; 9 | 10 | type Lang = Array<any>; 11 | 12 | //递增的id 13 | let uuid = 0; 14 | 15 | export class QueryLang { 16 | _id: number; 17 | _lang: Lang; 18 | _name: string; 19 | 20 | /** 21 | * init 22 | */ 23 | constructor(name: string, lang: Lang) { 24 | this._id = ++uuid; 25 | this._name = name; 26 | this._lang = lang; 27 | 28 | if (process.env.NODE_ENV != 'production') { 29 | if (!isQuery(this._lang)) { 30 | throw new Error(`${this._name} is not invalid`) 31 | } 32 | } 33 | } 34 | 35 | /** 36 | * 判断当前是不是一个合法的query lang 37 | * @returns {boolean} 38 | */ 39 | isValidQuery(): boolean { 40 | return isQuery(this._lang); 41 | } 42 | 43 | /** 44 | * 当前的id 45 | * @returns {number} 46 | */ 47 | id(): number { 48 | return this._id; 49 | } 50 | 51 | /** 52 | * 当前的name 53 | */ 54 | name(): string { 55 | return this._name; 56 | } 57 | 58 | /** 59 | * 当前的语法标记 60 | * @returns {Array.<Object>} 61 | */ 62 | lang() { 63 | return this._lang; 64 | } 65 | 66 | setLang(lang: Lang) { 67 | this._lang = lang; 68 | return this; 69 | } 70 | } 71 | 72 | //export factory method 73 | export const QL = ( 74 | name: string, 75 | lang: Lang 76 | ) => new QueryLang(name, lang); 77 | -------------------------------------------------------------------------------- /examples/todo/src/actor/todo-actor.ts: -------------------------------------------------------------------------------- 1 | import { Actor, Action, IMap } from 'iflux2' 2 | import { fromJS, List } from 'immutable' 3 | 4 | interface Todo { 5 | id: number; 6 | text: string; 7 | done: boolean; 8 | } 9 | 10 | let uuid = 0 11 | 12 | export default class TodoActor extends Actor { 13 | defaultState() { 14 | return { 15 | todo: [] 16 | } 17 | } 18 | 19 | @Action('submit') 20 | submit(state: IMap, text: string) { 21 | return state.update('todo', (todo: List<IMap>) => todo.push(fromJS({ 22 | id: ++uuid, 23 | text, 24 | done: false 25 | }))) 26 | } 27 | 28 | 29 | @Action('toggle') 30 | toggle(state: IMap, index: number) { 31 | return state.updateIn(['todo', index, 'done'], done => !done) 32 | } 33 | 34 | 35 | @Action('destroy') 36 | destroy(state: IMap, index: number) { 37 | return state.deleteIn(['todo', index]) 38 | } 39 | 40 | 41 | @Action('toggleAll') 42 | toggleAll(state: IMap, checked: boolean) { 43 | return state.update('todo', 44 | (todo: List<IMap>) => todo.map((v: IMap) => v.set('done', checked))) 45 | } 46 | 47 | 48 | @Action('clearCompleted') 49 | clearCompleted(state: IMap) { 50 | return state.update('todo', 51 | todo => todo.filter((v: IMap) => !v.get('done'))) 52 | } 53 | 54 | @Action('init') 55 | init(state: IMap, todo: Todo) { 56 | return state.set('todo', fromJS(todo)) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /documents/gitbook/store.md: -------------------------------------------------------------------------------- 1 | # Store 2 | 3 | Store, 我们的数据状态容器中心,管理着整个app的数据的生命周期。 4 | 我们坚守单根数据源的思想(single data source),store中保持着完整的页面需要的状态 5 | 6 | 7 | 在iflux2中store的主要职责有哪些? 8 | 9 | 1. 聚合actor 10 | 2. 分派actor 11 | 3. 通过bigQuery计算我们的查询语言(QL) 12 | 4. 响应页面的事件(ActionCreator) 13 | 5. 计算流程的控制(dispatch => actor) 14 | 15 | 16 | # Talk is cheap, Show me code. 17 | 18 | 19 | ```js 20 | import {Store} from 'iflux2' 21 | import LoadingActor from 'loading-actor' 22 | import UserActor from 'user-actor' 23 | import TodoActor from 'todo-actor' 24 | 25 | class AppStore extends Store { 26 | //聚合Actor 27 | bindActor() { 28 | return [ 29 | new LoadingActor, 30 | new UserActor, 31 | new TodoActor 32 | ] 33 | } 34 | 35 | //;;;;;;;;;;;;;action;;;;;;;;;;;;;; 36 | update = () => { 37 | //通过dispatch分派到actor 38 | this.dispatch('update') 39 | }; 40 | 41 | save = () => { 42 | //通过dispatch分派到actor 43 | this.dispatch('save') 44 | }; 45 | } 46 | ``` 47 | 48 | ##关键API 49 | 50 | //获取从actor聚合出来的页面需要的状态数据(immutable) 51 | state() 52 | 53 | //非常重要的方法,计算QL 54 | bigQuery() 55 | 56 | //分派计算任务给actor处理 57 | dispatch() 58 | 59 | 60 | //;;;;;;;;;;;;;;;;;;辅助方法;;;;;;;;;;;;;;;;;;;;;; 61 | pprint() //打印出来state()的值,辅助我们查看state 62 | 63 | pprintActor() //打印出{actorName: actor defaultState ....} 64 | 65 | pprintBigQuery() //打印出bigQuery的计算结果,辅助调试 66 | 67 | 68 | ## source 69 | [source](http://git.dev.qianmi.com/OF730/iflux2/blob/master/src/store.js) 70 | -------------------------------------------------------------------------------- /perf/reduce-state.perf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 科学的方式分析改进系统的性能问题 3 | * M.P.D.I.A 4 | * M: Measure 5 | * P: Profile 6 | * D: Diagnose 7 | * I: Indentify 8 | * A: Attemp 9 | * 10 | * 通过timeline迅速了解系统的整个时间流 11 | */ 12 | const Immutable = require('immutable'); 13 | const {fromJS, OrderedMap} = Immutable; 14 | 15 | 16 | /** 17 | * 旧的reduceState的算法 18 | */ 19 | const reduceState = (actorState) => { 20 | return actorState.valueSeq().reduce((init, value) => { 21 | return init.merge(value); 22 | }, OrderedMap()); 23 | }; 24 | 25 | /** 26 | * 新的reduceState算法 27 | */ 28 | const newReduceState = (actorState) => { 29 | return OrderedMap().update(value => { 30 | return actorState.valueSeq().reduce((init, state) => { 31 | return init.merge(state); 32 | }, value); 33 | }); 34 | }; 35 | 36 | //mock data 37 | const actorState = fromJS({ 38 | 1: { 39 | storeId:1, 40 | storeName: 'App-Store', 41 | }, 42 | 2: { 43 | productId: 1, 44 | productName: 'test', 45 | }, 46 | 3: { 47 | goodsId: 1, 48 | goodsName: 'test red', 49 | specList: [ 50 | {1: 'red'}, 51 | {2: 'xxl'}, 52 | {3: '100G'} 53 | ] 54 | } 55 | }); 56 | 57 | 58 | /** 59 | * origin#reduceState: 2.532ms 60 | * new#reduceState: 0.227ms 61 | */ 62 | 63 | console.time('origin#reduceState'); 64 | reduceState(actorState); 65 | console.timeEnd('origin#reduceState'); 66 | 67 | console.time('new#reduceState'); 68 | reduceState(actorState); 69 | console.timeEnd('new#reduceState'); -------------------------------------------------------------------------------- /examples/blog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog", 3 | "version": "1.0.0", 4 | "description": "iflux2 react-router", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest --verbose", 8 | "start": "./node_modules/.bin/webpack-dev-server" 9 | }, 10 | "keywords": [ 11 | "iflux2", 12 | "react-router" 13 | ], 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/hufeng/iflux2-blog.git" 17 | }, 18 | "author": "hufeng", 19 | "license": "ISC", 20 | "dependencies": { 21 | "@types/react": "^15.0.14", 22 | "@types/react-dom": "^0.14.23", 23 | "iflux2": "1.4.4", 24 | "react": "15.4.2", 25 | "react-dom": "15.4.2", 26 | "react-router": "^2.6.1", 27 | "whatwg-fetch": "^1.0.0" 28 | }, 29 | "devDependencies": { 30 | "babel-jest": "^13.0.0", 31 | "babel-loader": "^6.2.4", 32 | "babel-plugin-transform-class-properties": "^6.10.2", 33 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 34 | "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", 35 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 36 | "babel-plugin-transform-runtime": "^6.9.0", 37 | "babel-polyfill": "^6.9.1", 38 | "babel-preset-es2015": "^6.9.0", 39 | "babel-preset-react": "^6.11.1", 40 | "babel-preset-stage-3": "^6.11.0", 41 | "html-webpack-plugin": "^2.21.0", 42 | "ts-loader": "^2.0.1", 43 | "typescript": "^2.2.1", 44 | "webpack": "^2.1.0-beta.15", 45 | "webpack-dev-server": "^1.14.1" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /types/validator.js.flow: -------------------------------------------------------------------------------- 1 | //@flow 2 | 3 | //校验的规则参数 4 | export type ValidatorOptions = { 5 | oneError?: boolean; 6 | debug?: boolean; 7 | validateFields: Array<string>; 8 | } 9 | 10 | declare type ValidatorResult = { 11 | result: boolean; 12 | errors: {[key: string]: Array<string>}; 13 | } 14 | 15 | declare class Validator { 16 | static validate( 17 | obj: Object, 18 | rules: Object, 19 | options: ValidatorOptions 20 | ): ValidatorResult; 21 | 22 | static email(value: string): boolean; 23 | static url(value: string): boolean; 24 | static date(value: string): boolean; 25 | static number(value: string): boolean; 26 | static digits(value: string): boolean; 27 | static required(value: string): boolean; 28 | static cardNo(value: string): boolean; 29 | static qq(value: string): boolean; 30 | static mobile(value: string): boolean; 31 | static zipCode(value: string): boolean; 32 | 33 | static phone(value: string): boolean; 34 | static pwdMix(value: string): boolean; 35 | static min(param: number, value: string): boolean; 36 | static max(param: number, value: string): boolean; 37 | static minLength(param: number, value: string): boolean; 38 | static maxLength(param: number, value: string): boolean; 39 | static range(param: [number, number], value: string): string; 40 | 41 | static rangeLength(param: [number, number], val: string): boolean; 42 | static forbbidenChar(value: string): boolean; 43 | static addValidator(name: string, callback: Function): void; 44 | } 45 | 46 | export default Validator; 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of iflux2 nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import <React/RCTBundleURLProvider.h> 13 | #import <React/RCTRootView.h> 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 22 | 23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 24 | moduleName:@"AwesomeApp" 25 | initialProperties:nil 26 | launchOptions:launchOptions]; 27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 28 | 29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 30 | UIViewController *rootViewController = [UIViewController new]; 31 | rootViewController.view = rootView; 32 | self.window.rootViewController = rootViewController; 33 | [self.window makeKeyAndVisible]; 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /examples/todo/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, 胡锋 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of iflux2-todo nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /examples/AwesomeApp/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | <PROJECT_ROOT>/\.buckd/ 7 | 8 | ; Ignore unexpected extra "@providesModule" 9 | .*/node_modules/.*/node_modules/fbjs/.* 10 | 11 | ; Ignore duplicate module providers 12 | ; For RN Apps installed via npm, "Libraries" folder is inside 13 | ; "node_modules/react-native" but in the source repo it is in the root 14 | .*/Libraries/react-native/React.js 15 | .*/Libraries/react-native/ReactNative.js 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/Libraries/react-native/react-native-interface.js 21 | node_modules/react-native/flow 22 | flow/ 23 | 24 | [options] 25 | emoji=true 26 | 27 | module.system=haste 28 | 29 | experimental.strict_type_args=true 30 | 31 | munge_underscores=true 32 | 33 | module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub' 34 | 35 | suppress_type=$FlowIssue 36 | suppress_type=$FlowFixMe 37 | suppress_type=$FixMe 38 | 39 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(3[0-8]\\|[1-2][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) 40 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(3[0-8]\\|1[0-9]\\|[1-2][0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ 41 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy 42 | 43 | unsafe.enable_getters_and_setters=true 44 | 45 | [version] 46 | ^0.38.0 47 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplate/AppDelegate.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "AppDelegate.h" 11 | 12 | #import "RCTBundleURLProvider.h" 13 | #import "RCTRootView.h" 14 | 15 | @implementation AppDelegate 16 | 17 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 18 | { 19 | NSURL *jsCodeLocation; 20 | 21 | jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index.ios" fallbackResource:nil]; 22 | 23 | RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 24 | moduleName:@"iflux2NativeTemplate" 25 | initialProperties:nil 26 | launchOptions:launchOptions]; 27 | rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1]; 28 | 29 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 30 | UIViewController *rootViewController = [UIViewController new]; 31 | rootViewController.view = rootView; 32 | self.window.rootViewController = rootViewController; 33 | [self.window makeKeyAndVisible]; 34 | return YES; 35 | } 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /examples/blog/apps/edit/component/edit.tsx: -------------------------------------------------------------------------------- 1 | //@flow 2 | import * as React from 'react' 3 | import { Relax } from 'iflux2' 4 | 5 | type Handler = () => void 6 | 7 | const noop = () => { } 8 | 9 | @Relax 10 | export default class Edit extends React.Component<any, any> { 11 | props: { 12 | title?: string; 13 | content?: string; 14 | submit?: Handler; 15 | changeTitle?: (text: string) => void; 16 | changeContent?: (text: string) => void; 17 | }; 18 | 19 | static defaultProps = { 20 | title: '', 21 | content: '', 22 | submit: noop, 23 | changeTitle: noop, 24 | changeContent: noop 25 | }; 26 | 27 | render() { 28 | const { title, content, submit } = this.props 29 | 30 | return ( 31 | <form onSubmit={submit}> 32 | <fieldset> 33 | <legend>new Blog:</legend> 34 | title: 35 | <input 36 | type="text" 37 | value={title} 38 | onChange={this._handleChangeTitle} 39 | /> 40 | <br /> 41 | content: 42 | <textarea 43 | value={content} 44 | onChange={this._handleContentChange} 45 | /> 46 | <br /> 47 | <input type="submit" value="post" /> 48 | </fieldset> 49 | </form> 50 | ) 51 | } 52 | 53 | _handleChangeTitle = (e: React.ChangeEvent<HTMLInputElement>) => { 54 | this.props.changeTitle(e.target.value) 55 | }; 56 | 57 | 58 | _handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { 59 | this.props.changeContent(e.target.value) 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /types/validator.d.ts: -------------------------------------------------------------------------------- 1 | export = Validator; 2 | 3 | declare class Validator { 4 | static validate( 5 | obj: Object, 6 | rules: Object, 7 | options: Validator.ValidatorOptions 8 | ): Validator.ValidatorResult; 9 | 10 | static email(value: string): boolean; 11 | static url(value: string): boolean; 12 | static date(value: string): boolean; 13 | static number(value: string): boolean; 14 | static digits(value: string): boolean; 15 | static required(value: string): boolean; 16 | static cardNo(value: string): boolean; 17 | static qq(value: string): boolean; 18 | static mobile(value: string): boolean; 19 | static zipCode(value: string): boolean; 20 | 21 | static phone(value: string): boolean; 22 | static pwdMix(value: string): boolean; 23 | static min(param: number, value: string): boolean; 24 | static max(param: number, value: string): boolean; 25 | static minLength(param: number, value: string): boolean; 26 | static maxLength(param: number, value: string): boolean; 27 | static range(param: [number, number], value: string): string; 28 | 29 | static rangeLength(param: [number, number], val: string): boolean; 30 | static forbbidenChar(value: string): boolean; 31 | static addValidator(name: string, callback: Function): void; 32 | } 33 | 34 | 35 | declare namespace Validator { 36 | export interface ValidatorOptions { 37 | oneError?: boolean; 38 | debug?: boolean; 39 | validateFields: Array<string>; 40 | } 41 | 42 | export interface ValidatorResult { 43 | result: boolean; 44 | errors: { [key: string]: Array<string> }; 45 | } 46 | } -------------------------------------------------------------------------------- /__tests__/test-relax.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as renderer from 'react-test-renderer'; 3 | import { Actor, Action, Store, Relax, StoreProvider } from "../src/index"; 4 | jest.mock('react-dom'); 5 | 6 | //;;;;;;;;;;Actor;;;;;;;;;;;;;;;;;; 7 | class TestActor extends Actor { 8 | defaultState() { 9 | return { 10 | text: 'hello world' 11 | } 12 | } 13 | 14 | @Action('init') 15 | init(state, text) { 16 | return state.set('text', text); 17 | } 18 | } 19 | 20 | 21 | //;;;;;;;;;;;;;;;Store;;;;;;;;;;;;;;; 22 | class AppStore extends Store { 23 | bindActor() { 24 | return [ 25 | new TestActor() 26 | ] 27 | } 28 | 29 | init = () => { 30 | this.dispatch('init', 'hello iflux2'); 31 | }; 32 | } 33 | 34 | //;;;;;;;;;;;;;;;;Relax;;;;;;;;;;;;;;; 35 | @Relax 36 | class Text extends React.Component { 37 | props: { 38 | text: string; 39 | }; 40 | 41 | static defaultProps = { 42 | text: '' 43 | }; 44 | 45 | render() { 46 | expect(this.props.text).toEqual('hello iflux2'); 47 | return ( 48 | <div>{this.props.text}</div> 49 | ) 50 | } 51 | } 52 | 53 | //;;;;;;;;;;;;;;;;;;root;;;;;;;;;;;;; 54 | @StoreProvider(AppStore) 55 | class HelloApp extends React.Component { 56 | props: { 57 | store: AppStore; 58 | }; 59 | 60 | componentWillMount() { 61 | this.props.store.init(); 62 | } 63 | 64 | render() { 65 | return ( 66 | <Text /> 67 | ) 68 | } 69 | } 70 | 71 | //;;;;;;;;;;;test;;;;;;;;;;;;;;;;;;;;; 72 | test('test store provider and relax', () => { 73 | const tree = renderer.create(<HelloApp />).toJSON(); 74 | expect(tree).toMatchSnapshot(); 75 | }); -------------------------------------------------------------------------------- /examples/validator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "validate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.production.js", 9 | "start": "./node_modules/.bin/webpack-dev-server --port 3000 --config webpack.config.js" 10 | }, 11 | "dependencies": { 12 | "@types/react": "^15.0.14", 13 | "@types/react-dom": "^0.14.23", 14 | "iflux2": "1.4.4", 15 | "react": "^15.1.0", 16 | "react-dom": "^15.1.0" 17 | }, 18 | "author": "hufeng", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "babel-core": "^6.17.0", 22 | "babel-loader": "^6.2.4", 23 | "babel-plugin-syntax-async-functions": "^6.8.0", 24 | "babel-plugin-transform-class-properties": "^6.10.2", 25 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 26 | "babel-plugin-transform-es2015-modules-commonjs": "^6.10.3", 27 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 28 | "babel-plugin-transform-react-constant-elements": "^6.9.1", 29 | "babel-plugin-transform-react-inline-elements": "^6.8.0", 30 | "babel-plugin-transform-runtime": "^6.9.0", 31 | "babel-preset-es2015": "^6.9.0", 32 | "babel-preset-react": "^6.5.0", 33 | "babel-preset-stage-3": "^6.5.0", 34 | "css-loader": "^0.23.1", 35 | "html-webpack-plugin": "^2.22.0", 36 | "react-addons-perf": "^15.3.0", 37 | "style-loader": "^0.13.1", 38 | "ts-loader": "^2.0.1", 39 | "typescript": "^2.2.1", 40 | "webpack": "^2.1.0-beta.15", 41 | "webpack-dev-server": "^1.14.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /__tests__/test-actor.ts: -------------------------------------------------------------------------------- 1 | import { fromJS, is } from 'immutable'; 2 | import { Actor, Action } from "../src/index"; 3 | 4 | describe('default actor', () => { 5 | it('defaultState is equal {}', () => { 6 | const defaultActor = new Actor(); 7 | expect(defaultActor.defaultState()).toEqual({}); 8 | }); 9 | }); 10 | 11 | 12 | //;;;;;;;;;;;;;;;UserActor;;;;;;;;;;;;;;;;;;;;;; 13 | class UserActor extends Actor { 14 | defaultState() { 15 | return { 16 | id: 1, 17 | username: 'iflux2', 18 | age: 1, 19 | email: 'iflux@qianmi.com' 20 | }; 21 | } 22 | 23 | @Action('init') 24 | init(state) { 25 | return state; 26 | } 27 | 28 | @Action('_change:age') 29 | changeAge(state, age) { 30 | return state.set('age', age); 31 | } 32 | } 33 | 34 | //;;;;;;;;;;;;;;;;;;init;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 35 | const user = new UserActor(); 36 | 37 | //;;;;;;;;;;;;;;;test;;;;;;;;;;;;;;;;;;;;; 38 | describe('actor test suite', () => { 39 | it('default state test', () => { 40 | expect(user.defaultState()) 41 | .toEqual({ 42 | id: 1, 43 | username: 'iflux2', 44 | age: 1, 45 | email: 'iflux@qianmi.com' 46 | }) 47 | }); 48 | 49 | it('decorator test', () => { 50 | expect(user.init).toEqual(user._route['init']); 51 | }); 52 | 53 | it('receive test', () => { 54 | const state = fromJS({ 55 | id: 1, 56 | username: 'iflux2', 57 | age: 1, 58 | email: 'iflux@qianmi.com' 59 | }); 60 | 61 | const newState = user.receive('init', state); 62 | expect(state === newState).toEqual(true); 63 | 64 | //change age test 65 | expect(user.receive('_change:age', state, 10).get('age')).toEqual(10); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /__tests__/test-transaction-rollback.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Store, Actor, Action } from "../src/index"; 3 | import { Map } from 'immutable' 4 | 5 | type IMap = Map<string, any> 6 | 7 | class HelloActor extends Actor { 8 | defaultState() { 9 | return { text: 'hello' } 10 | } 11 | 12 | @Action('change') 13 | change(state: IMap, text) { 14 | throw new Error('change exception') 15 | return state.set('text', text) 16 | } 17 | } 18 | 19 | class LoadingActor extends Actor { 20 | defaultState() { 21 | return { loading: false } 22 | } 23 | 24 | @Action('loading:end') 25 | change(state: IMap) { 26 | return state.update('loading', loading => !loading) 27 | } 28 | } 29 | 30 | 31 | class AppStore extends Store { 32 | bindActor() { 33 | return [ 34 | new LoadingActor, 35 | new HelloActor 36 | ] 37 | } 38 | 39 | rollBack = () => { 40 | this.transaction(() => { 41 | this.dispatch('loading:end') 42 | this.dispatch('change', 'hello iflux2') 43 | }) 44 | } 45 | 46 | change = () => { 47 | this.transaction(() => { 48 | try { 49 | this.dispatch('loading:end') 50 | this.dispatch('change', 'hello iflux2') 51 | } catch (err) { } 52 | }) 53 | } 54 | } 55 | 56 | 57 | describe('test store transaction fail rollback', () => { 58 | it('test rollback', () => { 59 | const store = new AppStore({}) 60 | const state = store.state() 61 | store.rollBack() 62 | expect(store.state()).toEqual(state) 63 | }) 64 | 65 | it('test without rollback', () => { 66 | const store = new AppStore({ debug: true }) 67 | const state = store.state() 68 | store.change() 69 | expect(store.state() == state).toEqual(false) 70 | }); 71 | }) -------------------------------------------------------------------------------- /documents/gitbook/ql.md: -------------------------------------------------------------------------------- 1 | # QL 2 | 3 | QL = Query Lang 4 | 5 | 自定义查询语法,数据的源头是store的state()返回的数据 6 | 7 | 8 | ## Syntax 9 | QL(displayName, [string|array|QL..., fn]) 10 | 11 | displayName,主要是帮助我们在debug状态更好地日志跟踪 12 | 13 | string|array|QL: string|array都是immutable的get的path, QL其他的QL(支持无限嵌套) 14 | 15 | 例如: 16 | ```js 17 | import {OrderedMap) from 'immutable' 18 | 19 | const user = OrderMap({ 20 | id: 1, 21 | name: 'iflux2', 22 | address: { 23 | city: '南京' 24 | } 25 | }) 26 | 27 | const id = user.get('id') //path => id 28 | const name = user.get('name') //path => name 29 | const city = user.getIn(['address', 'city']) //path => ['address', 'city'] 30 | ``` 31 | 32 | fn: 可计算状态的回调函数,bigQuery会取得所有的所有的数组中的path对应的值,作为参数传递给fn,例如: 33 | 34 | 35 | ```js 36 | /** 37 | * 返回:{ 38 | * id: 1, 39 | * name: 'iflux2', 40 | * address: { 41 | * city: '南京' 42 | * } 43 | *} 44 | */ 45 | store.state() 46 | 47 | // QL计算的结果值是 “iflux2南京" 48 | const ifluxQL = QL('ifluxQL', [ 49 | 'name', 50 | ['address', 'city'], 51 | (name, city) => `${name}${city}` 52 | ]) 53 | 54 | store.bigQuery(ifluxQL) //iflux2南京 55 | ``` 56 | 57 | ## QL in QL 58 | 59 | ```js 60 | import {QL} from 'iflux2' 61 | 62 | const loadingQL = QL('loadingQL', [ 63 | 'loading', 64 | loading => loading 65 | ]) 66 | 67 | const userQL = QL('userQL', [ 68 | //query lang 支持嵌套 69 | loadingQL, 70 | ['user', 'id'], 71 | (id, loading) => ({id, loading}) 72 | ]) 73 | ``` 74 | 75 | ## why? 76 | 77 | 为什么我们需要一个QL 78 | 1. 如果我们把我们的state看成source data,我们需要有派生数据的能力,因为UI展示的数据,可能需要根据我们的源数据进行组合 79 | 80 | 2. 我们需要我们的UI的数据具有reactive的能力,当source data变化的时候,@Relax会去重新计算我们的QL,达到数据Reactive的能力 81 | 82 | 3. 是的,命令式的编程手动的精确的处理数据之间的依赖和更新,Reactive会不够精确,导致同一个QL可能会被执行多次,造成计算上的浪费,不过不需要担心,我们已经添加了合适的cache,确保path对应的数据没有变化的时候,QL不会重复计算 83 | -------------------------------------------------------------------------------- /__tests__/test-util.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isArray, 3 | isStr, 4 | isFn, 5 | filterActorConflictKey 6 | } from '../src/util'; 7 | import {QL} from '../src/ql'; 8 | import Actor from '../src/actor' 9 | 10 | 11 | describe('util test suite', () => { 12 | it('it should be array', () => { 13 | expect(true).toEqual( 14 | isArray([1,2,3]) 15 | ); 16 | 17 | expect(false).toEqual( 18 | isArray({}) 19 | ); 20 | }); 21 | 22 | it('it should be string', () => { 23 | expect(true).toEqual( 24 | isStr('hello') 25 | ); 26 | 27 | expect(false).toEqual( 28 | isStr(1323) 29 | ); 30 | }); 31 | 32 | it('it should be fn', () => { 33 | expect(true).toEqual( 34 | isFn(() => {}) 35 | ); 36 | 37 | expect(false).toEqual( 38 | isFn(123) 39 | ); 40 | }); 41 | 42 | it('actorFilterConflict', () => { 43 | 44 | expect([]).toEqual( 45 | filterActorConflictKey([]) 46 | ) 47 | 48 | class User extends Actor { 49 | defaultState() { 50 | return { 51 | id: 1, 52 | name: '', 53 | email: '' 54 | } 55 | } 56 | } 57 | 58 | class Address extends Actor { 59 | defaultState() { 60 | return { 61 | id: 1, 62 | addressName: '' 63 | } 64 | } 65 | } 66 | 67 | class Concat extends Actor { 68 | defaultState() { 69 | return { 70 | id: 1, 71 | addressName: '' 72 | } 73 | } 74 | } 75 | 76 | const key = filterActorConflictKey([ 77 | new User, 78 | new Address, 79 | new Concat 80 | ]); 81 | 82 | expect([ 83 | ['id', ['User', 'Address', 'Concat']], 84 | ['addressName', ['Address', 'Concat']] 85 | ]).toEqual(key) 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /documents/gitbook/dql.md: -------------------------------------------------------------------------------- 1 | # DQL 2 | 3 | DQL = Dynamic Query Lang 4 | 5 | 自定义查询语法,数据的源头是store的state()返回的数据 6 | 7 | # Why? 8 | 9 | 为什么有了QL还需要DQL,主要是两个场景,QL的最大限制是依赖的state的数据路径必须是确定的,不可以动态设置参数的路径 10 | 而DQL可以是动态的路径,DQL的解析器会根据上下文的参数,替换动态参数,替换完返回QL,然后bigQuery计算出结果, 11 | __动态参数以$开头__ 12 | 13 | 14 | ## Syntax 15 | DQL(displayName, [string|array|QL..., fn]) 16 | 17 | displayName,查询语言名字,主要是帮助我们在debug状态更好地日志跟踪 18 | 19 | string|array|QL: string|array都是immutable的get的path, QL其他的QL(支持无限嵌套) 20 | 21 | 例如: 22 | ```js 23 | import {OrderedMap) from 'immutable' 24 | 25 | const user = OrderMap({ 26 | id: 1, 27 | name: 'iflux2', 28 | address: { 29 | city: '南京' 30 | } 31 | }) 32 | 33 | const id = user.get('id') //path => id 34 | const name = user.get('name') //path => name 35 | const city = user.getIn(['address', 'city']) //path => ['address', 'city'] 36 | ``` 37 | 38 | fn: 可计算状态的回调函数,bigQuery会取得所有的所有的数组中的path对应的值,作为参数传递给fn,例如: 39 | 40 | 41 | 42 | 假设我们的state: 43 | 44 | store.state() 45 | 46 | ```js 47 | { 48 | loading: true, 49 | todo: [ 50 | {id: 1, name: 'iflux2', done: true}, 51 | {id: 2, name: 'Rust', done: false} 52 | {id: 3, name: 'Ocaml', done: false} 53 | ] 54 | } 55 | ``` 56 | 57 | ```js 58 | const todoDQL = QL('todoDQL', [ 59 | ['todo', '$index', 'name'], 60 | (name) => name 61 | ]); 62 | 63 | const todoQL = todoDQL.context({ 64 | id: 1 65 | }); 66 | 67 | store.bigQuery(todoQL); // 返回Rust 68 | 69 | ``` 70 | 71 | ## QL in DQL 72 | 73 | 74 | ```js 75 | import {QL, DQL} from 'iflux2' 76 | 77 | const loadingQL = QL('loadingQL', [ 78 | 'loading', 79 | loading => loading 80 | ]) 81 | 82 | const todoDQL = QL('todoDQL', [ 83 | //query lang 支持嵌套 84 | loadingQL, 85 | ['todo', '$id', 'name'], 86 | (loading, name) => { 87 | return loading ? name : ''; 88 | } 89 | ]) 90 | ``` 91 | -------------------------------------------------------------------------------- /examples/todo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.production.js", 9 | "start": "./node_modules/.bin/webpack-dev-server --port 3000 --config webpack.config.js" 10 | }, 11 | "dependencies": { 12 | "@types/react": "^15.0.14", 13 | "@types/react-dom": "^0.14.23", 14 | "iflux2": "^1.4.4", 15 | "react": "15.4.2", 16 | "react-dom": "15.4.2" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/hufeng/iflux2-todo.git" 21 | }, 22 | "author": "hufeng", 23 | "license": "ISC", 24 | "devDependencies": { 25 | "babel-core": "^6.17.0", 26 | "babel-loader": "^6.3.2", 27 | "babel-plugin-syntax-async-functions": "^6.8.0", 28 | "babel-plugin-transform-class-properties": "^6.23.0", 29 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 30 | "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", 31 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 32 | "babel-plugin-transform-react-constant-elements": "^6.23.0", 33 | "babel-plugin-transform-react-inline-elements": "^6.22.0", 34 | "babel-plugin-transform-runtime": "^6.23.0", 35 | "babel-preset-es2015": "^6.22.0", 36 | "babel-preset-react": "^6.23.0", 37 | "babel-preset-stage-3": "^6.22.0", 38 | "css-loader": "^0.26.2", 39 | "html-webpack-plugin": "^2.28.0", 40 | "react-addons-perf": "^15.4.2", 41 | "style-loader": "^0.13.2", 42 | "ts-jest": "^19.0.0", 43 | "ts-loader": "^2.0.1", 44 | "typescript": "^2.2.1", 45 | "webpack": "^2.2.1", 46 | "webpack-dev-server": "^2.4.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.awesomeapp', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.awesomeapp', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /examples/todo/src/component/main-section.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax, IMap } from 'iflux2' 3 | import { List } from 'immutable' 4 | import { todoQL, todoDQL } from '../ql' 5 | 6 | const noop = () => { } 7 | 8 | @Relax 9 | export default class MainSection extends React.Component<any, any> { 10 | props: { 11 | index?: number; 12 | todo?: List<IMap>, 13 | todoDQL?: IMap, 14 | toggle?: (index: number) => void; 15 | destroy?: (index: number) => void; 16 | toggleAll?: (checked: boolean) => void; 17 | }; 18 | 19 | static defaultProps = { 20 | index: 0,//假设是父组件传递的属性 21 | todo: todoQL, 22 | todoDQL: todoDQL, //会用index替换$index 23 | toggle: noop, 24 | destroy: noop, 25 | toggleAll: noop 26 | }; 27 | 28 | render() { 29 | const { toggle, toggleAll, destroy } = this.props 30 | //测试我们的dql 31 | console.log('test dql:', this.props.todoDQL.toString()); 32 | 33 | return ( 34 | <section className="main"> 35 | <input className="toggle-all" 36 | type="checkbox" 37 | onChange={(e) => toggleAll(e.target.checked)} /> 38 | <label htmlFor="toggle-all">Mark all as complete</label> 39 | <ul className="todo-list"> 40 | {this.props.todo.map((v, k) => 41 | <li key={v.get('id')}> 42 | <div className="view"> 43 | <input className="toggle" 44 | type="checkbox" 45 | checked={v.get('done')} 46 | onChange={() => toggle(k)} /> 47 | <label>{v.get('text')}</label> 48 | <button className="destroy" 49 | onClick={() => destroy(k)} /> 50 | </div> 51 | </li> 52 | )} 53 | </ul> 54 | </section> 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/BUCK: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | # To learn about Buck see [Docs](https://buckbuild.com/). 4 | # To run your application with Buck: 5 | # - install Buck 6 | # - `npm start` - to start the packager 7 | # - `cd android` 8 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 9 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 10 | # - `buck install -r android/app` - compile, install and run application 11 | # 12 | 13 | lib_deps = [] 14 | for jarfile in glob(['libs/*.jar']): 15 | name = 'jars__' + re.sub(r'^.*/([^/]+)\.jar$', r'\1', jarfile) 16 | lib_deps.append(':' + name) 17 | prebuilt_jar( 18 | name = name, 19 | binary_jar = jarfile, 20 | ) 21 | 22 | for aarfile in glob(['libs/*.aar']): 23 | name = 'aars__' + re.sub(r'^.*/([^/]+)\.aar$', r'\1', aarfile) 24 | lib_deps.append(':' + name) 25 | android_prebuilt_aar( 26 | name = name, 27 | aar = aarfile, 28 | ) 29 | 30 | android_library( 31 | name = 'all-libs', 32 | exported_deps = lib_deps 33 | ) 34 | 35 | android_library( 36 | name = 'app-code', 37 | srcs = glob([ 38 | 'src/main/java/**/*.java', 39 | ]), 40 | deps = [ 41 | ':all-libs', 42 | ':build_config', 43 | ':res', 44 | ], 45 | ) 46 | 47 | android_build_config( 48 | name = 'build_config', 49 | package = 'com.iflux2nativetemplate', 50 | ) 51 | 52 | android_resource( 53 | name = 'res', 54 | res = 'src/main/res', 55 | package = 'com.iflux2nativetemplate', 56 | ) 57 | 58 | android_binary( 59 | name = 'app', 60 | package_type = 'debug', 61 | manifest = 'src/main/AndroidManifest.xml', 62 | keystore = '//android/keystores:debug', 63 | deps = [ 64 | ':app-code', 65 | ], 66 | ) 67 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>$(PRODUCT_NAME)</string> 15 | <key>CFBundlePackageType</key> 16 | <string>APPL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1</string> 23 | <key>LSRequiresIPhoneOS</key> 24 | <true/> 25 | <key>UILaunchStoryboardName</key> 26 | <string>LaunchScreen</string> 27 | <key>UIRequiredDeviceCapabilities</key> 28 | <array> 29 | <string>armv7</string> 30 | </array> 31 | <key>UISupportedInterfaceOrientations</key> 32 | <array> 33 | <string>UIInterfaceOrientationPortrait</string> 34 | <string>UIInterfaceOrientationLandscapeLeft</string> 35 | <string>UIInterfaceOrientationLandscapeRight</string> 36 | </array> 37 | <key>UIViewControllerBasedStatusBarAppearance</key> 38 | <false/> 39 | <key>NSLocationWhenInUseUsageDescription</key> 40 | <string></string> 41 | <key>NSAppTransportSecurity</key> 42 | <!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ --> 43 | <dict> 44 | <key>NSExceptionDomains</key> 45 | <dict> 46 | <key>localhost</key> 47 | <dict> 48 | <key>NSExceptionAllowsInsecureHTTPLoads</key> 49 | <true/> 50 | </dict> 51 | </dict> 52 | </dict> 53 | </dict> 54 | </plist> 55 | -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeApp-tvOS/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>$(PRODUCT_NAME)</string> 15 | <key>CFBundlePackageType</key> 16 | <string>APPL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1</string> 23 | <key>LSRequiresIPhoneOS</key> 24 | <true/> 25 | <key>UILaunchStoryboardName</key> 26 | <string>LaunchScreen</string> 27 | <key>UIRequiredDeviceCapabilities</key> 28 | <array> 29 | <string>armv7</string> 30 | </array> 31 | <key>UISupportedInterfaceOrientations</key> 32 | <array> 33 | <string>UIInterfaceOrientationPortrait</string> 34 | <string>UIInterfaceOrientationLandscapeLeft</string> 35 | <string>UIInterfaceOrientationLandscapeRight</string> 36 | </array> 37 | <key>UIViewControllerBasedStatusBarAppearance</key> 38 | <false/> 39 | <key>NSLocationWhenInUseUsageDescription</key> 40 | <string></string> 41 | <key>NSAppTransportSecurity</key> 42 | <!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ --> 43 | <dict> 44 | <key>NSExceptionDomains</key> 45 | <dict> 46 | <key>localhost</key> 47 | <dict> 48 | <key>NSExceptionAllowsInsecureHTTPLoads</key> 49 | <true/> 50 | </dict> 51 | </dict> 52 | </dict> 53 | </dict> 54 | </plist> 55 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplate/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>CFBundleDevelopmentRegion</key> 6 | <string>en</string> 7 | <key>CFBundleExecutable</key> 8 | <string>$(EXECUTABLE_NAME)</string> 9 | <key>CFBundleIdentifier</key> 10 | <string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string> 11 | <key>CFBundleInfoDictionaryVersion</key> 12 | <string>6.0</string> 13 | <key>CFBundleName</key> 14 | <string>$(PRODUCT_NAME)</string> 15 | <key>CFBundlePackageType</key> 16 | <string>APPL</string> 17 | <key>CFBundleShortVersionString</key> 18 | <string>1.0</string> 19 | <key>CFBundleSignature</key> 20 | <string>????</string> 21 | <key>CFBundleVersion</key> 22 | <string>1</string> 23 | <key>LSRequiresIPhoneOS</key> 24 | <true/> 25 | <key>UILaunchStoryboardName</key> 26 | <string>LaunchScreen</string> 27 | <key>UIRequiredDeviceCapabilities</key> 28 | <array> 29 | <string>armv7</string> 30 | </array> 31 | <key>UISupportedInterfaceOrientations</key> 32 | <array> 33 | <string>UIInterfaceOrientationPortrait</string> 34 | <string>UIInterfaceOrientationLandscapeLeft</string> 35 | <string>UIInterfaceOrientationLandscapeRight</string> 36 | </array> 37 | <key>UIViewControllerBasedStatusBarAppearance</key> 38 | <false/> 39 | <key>NSLocationWhenInUseUsageDescription</key> 40 | <string></string> 41 | <key>NSAppTransportSecurity</key> 42 | <!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ --> 43 | <dict> 44 | <key>NSExceptionDomains</key> 45 | <dict> 46 | <key>localhost</key> 47 | <dict> 48 | <key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key> 49 | <true/> 50 | </dict> 51 | </dict> 52 | </dict> 53 | </dict> 54 | </plist> 55 | -------------------------------------------------------------------------------- /examples/validator/src/ql.ts: -------------------------------------------------------------------------------- 1 | 2 | import { QL } from 'iflux2' 3 | import * as Validator from 'iflux2/contrib/validator' 4 | 5 | export const validatorQL = QL('validatorQL', [ 6 | 'fields', 7 | 'username', 8 | 'password', 9 | 'confirm', 10 | 'email', 11 | 'qq', 12 | (fields, username, password, confirm, email, qq) => { 13 | //初始状态不校验 14 | if (fields.isEmpty()) { 15 | return { errors: {} } 16 | } 17 | 18 | const form = { 19 | username, 20 | password, 21 | confirm, 22 | email, 23 | qq 24 | } 25 | 26 | //添加自定义规则 27 | Validator.addValidator('equal', (fieldName, value) => { 28 | return form[fieldName] === value; 29 | }) 30 | 31 | const result = Validator.validate(form, { 32 | username: { 33 | required: true, 34 | maxLength: 10, 35 | message: { 36 | required: 'username is required', 37 | maxLength: 'username max length is 10' 38 | } 39 | }, 40 | password: { 41 | required: true, 42 | message: { 43 | required: 'password is required' 44 | } 45 | }, 46 | confirm: { 47 | required: true, 48 | equal: 'password', 49 | message: { 50 | required: 'confirm password is required.', 51 | equal: 'confirm password should be equal password' 52 | } 53 | }, 54 | email: { 55 | required: true, 56 | email: true, 57 | message: { 58 | required: 'email is required.', 59 | email: 'email is invalid' 60 | } 61 | }, 62 | qq: { 63 | required: true, 64 | qq: true, 65 | message: { 66 | required: 'qq is required.', 67 | qq: 'qq is invalid.' 68 | } 69 | } 70 | }, { debug: true, validateFields: fields.toArray() }); 71 | 72 | return result; 73 | } 74 | ]) 75 | -------------------------------------------------------------------------------- /__tests__/test-atom.ts: -------------------------------------------------------------------------------- 1 | import Atom from '../src/contrib/atom'; 2 | 3 | 4 | const appCache = new Atom({ 5 | token: 'auth fsdfjkferwerjk', 6 | skuCountList: { 7 | g1101: 9, 8 | g1102: 10 9 | } 10 | }); 11 | 12 | describe('atom test suite', () => { 13 | it('value test', () => { 14 | //path is array 15 | expect('auth fsdfjkferwerjk') 16 | .toEqual(appCache.value(['token'])); 17 | 18 | //path is string 19 | expect('auth fsdfjkferwerjk') 20 | .toEqual(appCache.value('token')); 21 | 22 | //path is array 23 | expect(9) 24 | .toEqual(appCache.value(['skuCountList', 'g1101'])); 25 | 26 | //path is undefined 27 | expect({ 28 | token: 'auth fsdfjkferwerjk', 29 | skuCountList: { 30 | g1101: 9, 31 | g1102: 10 32 | } 33 | }).toEqual( 34 | appCache.value().toJS() 35 | ) 36 | }); 37 | 38 | 39 | it('set value', () => { 40 | //覆盖if 41 | appCache.subscribe(); 42 | 43 | //newState == state 44 | appCache.cursor().set('token', 'auth fsdfjkferwerjk'); 45 | 46 | //可能发生的数据更新不同步的问题 47 | //withMutations是如此的重要 48 | const cursor = appCache.cursor(); 49 | cursor.set('token', 'test'); 50 | cursor.set('token', 'test`1'); 51 | 52 | 53 | appCache.subscribe((state) => { 54 | expect(100) 55 | .toEqual(state.getIn(['skuCountList', 'g1101'])); 56 | }); 57 | 58 | appCache 59 | .cursor() 60 | .setIn(['skuCountList', 'g1101'], 100); 61 | 62 | }); 63 | 64 | 65 | it('unsubscribe', () => { 66 | //覆盖line-93 67 | appCache.unsubscribe(); 68 | 69 | const callback = () => {}; 70 | appCache.subscribe(callback); 71 | expect(2).toEqual(appCache._callbacks.length); 72 | 73 | appCache.unsubscribe(callback); 74 | expect(1).toEqual(appCache._callbacks.length); 75 | }); 76 | 77 | it('pprint', () => { 78 | appCache.pprint(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /__tests__/test-react-dql.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import * as renderer from 'react-test-renderer' 3 | import { Map } from 'immutable' 4 | 5 | import { 6 | Actor, 7 | Store, 8 | StoreProvider, 9 | Relax, 10 | DQL, 11 | } from '../src/index' 12 | 13 | jest.mock('react-dom') 14 | 15 | type IMap = Map<string, any>; 16 | 17 | class ProductActor extends Actor { 18 | defaultState() { 19 | return { 20 | products: [ 21 | { id: 1, name: 'p1' }, 22 | { id: 2, name: 'p2' }, 23 | { id: 3, name: 'p3' }, 24 | { id: 4, name: 'p4' }, 25 | { id: 5, name: 'p5' }, 26 | { id: 6, name: 'p6' }, 27 | ] 28 | } 29 | } 30 | } 31 | 32 | class Appstore extends Store { 33 | bindActor() { 34 | return [new ProductActor] 35 | } 36 | } 37 | 38 | @StoreProvider(Appstore) 39 | class ProductApp extends React.Component { 40 | props: { 41 | store: Appstore; 42 | }; 43 | 44 | render() { 45 | const products = this.props.store.state().get('products') 46 | 47 | return ( 48 | <div> 49 | {products.map((p, index) => <ProductItem index={index} key={p.get('id')} />)} 50 | </div> 51 | ) 52 | } 53 | } 54 | 55 | const productDQL = DQL('productDQL', [ 56 | ['products', '$index'], 57 | p => p 58 | ]) 59 | 60 | @Relax 61 | class ProductItem extends React.Component { 62 | props: { 63 | product: IMap 64 | }; 65 | 66 | static defaultProps = { 67 | index: 0, 68 | product: productDQL 69 | }; 70 | 71 | render() { 72 | const { id, name } = this.props.product.toJS() 73 | 74 | return ( 75 | <div> 76 | <div>{id}</div> 77 | <div>{name}</div> 78 | </div> 79 | ) 80 | } 81 | } 82 | 83 | 84 | describe('react-dql test suite', () => { 85 | it('initial dql', () => { 86 | const tree = renderer.create(<ProductApp />).toJSON() 87 | expect(tree).toMatchSnapshot() 88 | }) 89 | }) -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 判断当前的参数是不是query-lang的合法形式 3 | * @param ql 4 | * @returns {boolean} 5 | */ 6 | import Actor from './actor' 7 | 8 | export function isQuery(ql: any): boolean { 9 | return isArray(ql) && isFn(ql[ql.length - 1]); 10 | } 11 | 12 | /** 13 | * 判断当前的参数是不是数组 14 | * @param arr 15 | * @returns {boolean} 16 | */ 17 | export function isArray(arr: any): boolean { 18 | return type(arr) === '[object Array]'; 19 | } 20 | 21 | /** 22 | * 是不是函数 23 | * @param fn 24 | * @returns {boolean} 25 | */ 26 | export function isFn(fn: any): boolean { 27 | return type(fn) === '[object Function]'; 28 | } 29 | 30 | /** 31 | * 是不是字符串 32 | * @param str 33 | */ 34 | export function isStr(str: any): boolean { 35 | return type(str) === '[object String]'; 36 | } 37 | 38 | export function isObject(str: any): boolean { 39 | return type(str) === '[object Object]'; 40 | } 41 | 42 | /** 43 | * 判断数据类型 44 | * @param type 45 | * @returns {string} 46 | */ 47 | export function type(type: any): string { 48 | return Object.prototype.toString.call(type); 49 | } 50 | 51 | /** 52 | * 过滤出actor中重复的key 53 | * @param actor 54 | * @returns Array 55 | */ 56 | export function filterActorConflictKey( 57 | actor: Array<Actor> = [] 58 | ): Array<[string, string]> { 59 | //返回冲突的key的数组 60 | let conflictKeyList = []; 61 | 62 | //如果数组的元素只有一个不判断 63 | if (actor.length <= 1) { 64 | return conflictKeyList; 65 | } 66 | 67 | //聚合数据 68 | let actorKeyMap = {}; 69 | for (let i = 0, len = actor.length; i < len; i++) { 70 | const actorName = actor[i].constructor.name; 71 | Object.keys(actor[i].defaultState()).forEach(v => { 72 | (actorKeyMap[v] || (actorKeyMap[v] = [])).push(actorName); 73 | }) 74 | } 75 | 76 | Object.keys(actorKeyMap).forEach(v => { 77 | const value = actorKeyMap[v]; 78 | if (value.length > 1) { 79 | conflictKeyList.push([v, value]); 80 | } 81 | }); 82 | 83 | return conflictKeyList; 84 | } 85 | -------------------------------------------------------------------------------- /examples/todo/src/component/footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import { Relax } from 'iflux2' 3 | import { countQL } from '../ql' 4 | 5 | const noop = () => { } 6 | 7 | @Relax 8 | export default class Footer extends React.Component<any, any> { 9 | props: { 10 | count?: number; 11 | filterStatus?: string; 12 | changeFilter?: (filter: string) => void; 13 | clearCompleted?: () => void; 14 | }; 15 | 16 | static defaultProps = { 17 | count: countQL, 18 | filterStatus: '', 19 | changeFilter: noop, 20 | clearCompleted: noop, 21 | }; 22 | 23 | render() { 24 | const { changeFilter, filterStatus, count, clearCompleted } = this.props 25 | let countText = '' 26 | 27 | if (count > 1) { 28 | countText = `${count} items left` 29 | } else if (count === 1) { 30 | countText = '1 item left' 31 | } 32 | 33 | return ( 34 | <footer className="footer"> 35 | <span className="todo-count">{countText}</span> 36 | <ul className="filters"> 37 | <li> 38 | <a href="javascript:;" 39 | className={"" === filterStatus ? 'selected' : ''} 40 | onClick={() => changeFilter('')}> 41 | All 42 | </a> 43 | </li> 44 | <li> 45 | <a href="javascript:;" 46 | className={"active" === filterStatus ? 'selected' : ''} 47 | onClick={() => changeFilter('active')}> 48 | Active 49 | </a> 50 | </li> 51 | <li> 52 | <a href="javacript:;" 53 | className={'completed' === filterStatus ? 'selected' : ''} 54 | onClick={() => changeFilter('completed')}> 55 | Completed 56 | </a> 57 | </li> 58 | </ul> 59 | <button 60 | className="clear-completed" 61 | onClick={clearCompleted}> 62 | Clear completed 63 | </button> 64 | </footer> 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /types/index.js.flow: -------------------------------------------------------------------------------- 1 | //@flow 2 | import { Map } from 'immutable' 3 | 4 | //store构造函数参数 5 | export type StoreOptions = { 6 | debug?: boolean; 7 | ctxStoreName?: string; 8 | }; 9 | 10 | //Immutable State 11 | export type IState = Map<string, any>; 12 | 13 | //当前的Actor的z状态类型 14 | export type ActorState = Map<string, any>; 15 | 16 | //定义redux类型的action 17 | export type ReduxAction = { 18 | type: string; 19 | }; 20 | 21 | declare type QueryLang = { 22 | id: () => number; 23 | name: () => string; 24 | lang: () => Object; 25 | isValidQuery(ql: QueryLang): boolean; 26 | }; 27 | 28 | declare class Actor { 29 | defaultState(): Object; 30 | } 31 | 32 | declare class Store { 33 | constructor(opts: StoreOptions): void; 34 | bindActor(): Array<Actor>; 35 | transaction(cb: Function): void; 36 | dispatch(msg: string | ReduxAction, param?: any): void; 37 | batchDispatch(actions: Array<string | [string, any] | ReduxAction>): void; 38 | bigQuery(ql: QueryLang): any; 39 | state(): IState; 40 | } 41 | 42 | declare var msg: events$EventEmitter; 43 | 44 | declare function QL( 45 | name: string, 46 | lang: Array<QueryLang | string | Array<string | number> | Function> | QueryLang 47 | ): QueryLang; 48 | 49 | declare type DynamicQueryLang = { 50 | analyserLang(lang: Array<any>): Array<any>; 51 | name(): string; 52 | lang(): Array<any>; 53 | context(ctx: Object): Object; 54 | }; 55 | 56 | declare function DQL( 57 | name: string, 58 | lang: Array<QueryLang | string | Array<string | number> | Function> | QueryLang 59 | ): DynamicQueryLang; 60 | 61 | declare function Action(name: string): Function; 62 | 63 | declare function CtxStoreName(name: string): Function; 64 | 65 | declare function StoreProvider( 66 | store: Store, 67 | opts: StoreOptions 68 | ): Function; 69 | 70 | declare function Relax( 71 | Component: ReactClass<{}> 72 | ): ReactClass<{}>; 73 | 74 | export { 75 | Actor, 76 | Action, 77 | CtxStoreName, 78 | QL, 79 | DQL, 80 | Relax, 81 | Store, 82 | StoreProvider, 83 | msg 84 | }; 85 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events' 2 | import { Map } from 'immutable' 3 | import * as React from 'react' 4 | 5 | export = iflux2 6 | 7 | declare namespace iflux2 { 8 | type Dispatch = () => void; 9 | type RollBack = () => void; 10 | 11 | export type IMap = Map<string, any> 12 | export type Handler = (state: IMap) => void; 13 | 14 | export interface IOptions { 15 | debug?: boolean; 16 | ctxStoreName?: string; 17 | } 18 | 19 | export interface ReduxAtion { 20 | type: string; 21 | } 22 | 23 | export class Actor { 24 | defaultState(): Object; 25 | } 26 | 27 | export class Store { 28 | constructor(props?: IOptions); 29 | dispatch(msg: string | ReduxAtion, params?: any): void; 30 | batchDispatch(actions: Array<[string, any] | ReduxAtion | string>): void; 31 | transaction(dispatch: Dispatch, rollBack?: RollBack): boolean; 32 | bindActor(): Array<Actor>; 33 | bigQuery(ql: QueryLang): any; 34 | state(): IMap; 35 | subscribe(cb: Handler): void; 36 | unsubscribe(cb: Handler): void; 37 | } 38 | 39 | export class QueryLang { 40 | constructor(name: string, lang: Array<any>); 41 | id(): number; 42 | name(): string; 43 | lang(): Array<any>; 44 | isValidQuery(): boolean; 45 | } 46 | 47 | export function QL( 48 | name: string, 49 | lang: Array<any> 50 | ): QueryLang; 51 | 52 | export class DynamicQueryLang { 53 | constructor(name: string, lang: Array<any>); 54 | name(): string; 55 | lang(): string; 56 | context(props: Object): this; 57 | analyserLang(lang: Array<any>): Array<any>; 58 | } 59 | 60 | export function DQL( 61 | name: string, 62 | lang: Array<any> 63 | ): DynamicQueryLang; 64 | 65 | export const msg: EventEmitter; 66 | 67 | export function Action(msg: string): Function; 68 | 69 | export function Relax<TFunction extends React.ComponentClass<any>>( 70 | target: TFunction 71 | ): TFunction; 72 | 73 | type TStore = typeof Store 74 | type Wrapper<IProps> = React.ComponentClass<IProps>; 75 | 76 | export function StoreProvider<TFunction extends React.ComponentClass<any>>( 77 | AppStore: TStore, 78 | opts?: IOptions 79 | ): (Base: TFunction) => any; 80 | } -------------------------------------------------------------------------------- /src/dql.ts: -------------------------------------------------------------------------------- 1 | import { QueryLang } from './ql'; 2 | import { isArray, isStr, isQuery } from './util'; 3 | 4 | type Lang = Array<any>; 5 | 6 | /** 7 | * 动态的QueryLang 8 | */ 9 | export class DynamicQueryLang { 10 | _ctx: Object; 11 | _name: string; 12 | _lang: Lang; 13 | 14 | constructor(name: string, lang: Lang) { 15 | this._ctx = {}; 16 | this._name = name; 17 | this._lang = lang; 18 | 19 | if (process.env.NODE_ENV != 'production') { 20 | if (!isQuery(this._lang)) { 21 | throw new Error(`${this._name} syntax error`) 22 | } 23 | } 24 | } 25 | 26 | /** 27 | * 分析路径中的动态元素,然后根据上下文替换 28 | * @param ql 29 | */ 30 | analyserLang(dLang: Lang) { 31 | const lang = [] 32 | 33 | for (let i = 0, len = dLang.length; i < len; i++) { 34 | //获取当前的路径 35 | let path = dLang[i]; 36 | 37 | if (isStr(path)) { 38 | lang[i] = path[0] === '$' ? this._ctx[path.substring(1)] : path; 39 | } else if (isArray(path)) { 40 | //init 41 | lang[i] = [] 42 | for (let j = 0, len = path.length; j < len; j++) { 43 | let field = dLang[i][j]; 44 | lang[i][j] = isStr(field) && field[0] === '$' ? this._ctx[field.substring(1)] : field; 45 | } 46 | } else if (path instanceof DynamicQueryLang) { 47 | lang[i] = new QueryLang(path._name + '2QL', this.analyserLang(path._lang)); 48 | } else { 49 | 50 | //zero runtime cost 51 | if (process.env.NODE_ENV != 'production') { 52 | //如果path是QueryLang,校验querylang语法的合法性 53 | if (path instanceof QueryLang && !path.isValidQuery()) { 54 | throw new Error(`DQL: syntax error`); 55 | } 56 | } 57 | 58 | lang[i] = path; 59 | } 60 | } 61 | 62 | 63 | return lang; 64 | } 65 | 66 | /** 67 | * 设置上下文 68 | * @param {Object} ctx 69 | */ 70 | context(ctx: Object) { 71 | this._ctx = ctx; 72 | return this; 73 | } 74 | 75 | name() { 76 | return this._name 77 | } 78 | 79 | lang() { 80 | return this._lang 81 | } 82 | } 83 | 84 | /** 85 | * 工厂函数 86 | * @param {string name 87 | * @param {Lang} lang 88 | */ 89 | export const DQL = ( 90 | name: string, 91 | lang: Lang 92 | ) => new DynamicQueryLang(name, lang); 93 | -------------------------------------------------------------------------------- /examples/timer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timer", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest --verbose", 8 | "test:coverage": "jest --coverage", 9 | "build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.production.js", 10 | "start": "./node_modules/.bin/webpack-dev-server --port 3000" 11 | }, 12 | "jest": { 13 | "globals": { 14 | "__DEV__": true 15 | }, 16 | "transform": { 17 | ".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js" 18 | }, 19 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "tsx", 23 | "js" 24 | ] 25 | }, 26 | "dependencies": { 27 | "@types/node": "^7.0.5", 28 | "@types/react": "^15.0.14", 29 | "@types/react-dom": "^0.14.23", 30 | "iflux2": "1.4.4", 31 | "react": "15.4.2", 32 | "react-dom": "15.4.2" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "" 37 | }, 38 | "author": "hufeng", 39 | "license": "ISC", 40 | "devDependencies": { 41 | "babel-core": "^6.23.1", 42 | "babel-jest": "^17.0.2", 43 | "babel-loader": "^6.3.2", 44 | "babel-plugin-syntax-async-functions": "^6.8.0", 45 | "babel-plugin-transform-class-properties": "^6.23.0", 46 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 47 | "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", 48 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 49 | "babel-plugin-transform-react-constant-elements": "^6.23.0", 50 | "babel-plugin-transform-react-inline-elements": "^6.22.0", 51 | "babel-plugin-transform-runtime": "^6.23.0", 52 | "babel-polyfill": "^6.16.0", 53 | "babel-preset-es2015": "^6.22.0", 54 | "babel-preset-react": "^6.23.0", 55 | "babel-preset-stage-3": "^6.22.0", 56 | "css-loader": "^0.26.2", 57 | "html-webpack-plugin": "^2.28.0", 58 | "jest": "^17.0.3", 59 | "react-addons-perf": "^15.4.2", 60 | "react-test-renderer": "^15.4.0", 61 | "style-loader": "^0.13.2", 62 | "ts-jest": "^19.0.0", 63 | "ts-loader": "^2.0.1", 64 | "typescript": "^2.2.1", 65 | "webpack": "^2.2.1", 66 | "webpack-dev-server": "^2.4.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/counter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "counter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest --verbose", 8 | "test:coverage": "jest --coverage", 9 | "build": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.production.js", 10 | "start": "./node_modules/.bin/webpack-dev-server --port 3000" 11 | }, 12 | "jest": { 13 | "globals": { 14 | "__DEV__": true 15 | }, 16 | "transform": { 17 | ".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js" 18 | }, 19 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 20 | "moduleFileExtensions": [ 21 | "ts", 22 | "tsx", 23 | "js" 24 | ] 25 | }, 26 | "dependencies": { 27 | "iflux2": "1.4.4", 28 | "react": "15.4.2", 29 | "react-dom": "15.4.2" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/hufeng/iflux2-counter.git" 34 | }, 35 | "author": "hufeng", 36 | "license": "ISC", 37 | "devDependencies": { 38 | "@types/react": "^15.0.14", 39 | "@types/react-dom": "^0.14.23", 40 | "babel-core": "^6.21.0", 41 | "babel-jest": "^17.0.2", 42 | "babel-loader": "^6.2.10", 43 | "babel-plugin-syntax-async-functions": "^6.8.0", 44 | "babel-plugin-transform-class-properties": "^6.19.0", 45 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 46 | "babel-plugin-transform-es2015-modules-commonjs": "^6.18.0", 47 | "babel-plugin-transform-flow-strip-types": "^6.21.0", 48 | "babel-plugin-transform-react-constant-elements": "^6.9.1", 49 | "babel-plugin-transform-react-inline-elements": "^6.8.0", 50 | "babel-plugin-transform-runtime": "^6.9.0", 51 | "babel-polyfill": "^6.16.0", 52 | "babel-preset-es2015": "^6.18.0", 53 | "babel-preset-react": "^6.5.0", 54 | "babel-preset-stage-3": "^6.5.0", 55 | "css-loader": "^0.26.1", 56 | "html-webpack-plugin": "^2.26.0", 57 | "jest": "^17.0.3", 58 | "react-addons-perf": "^15.4.2", 59 | "react-test-renderer": "^15.4.0", 60 | "style-loader": "^0.13.1", 61 | "ts-jest": "^19.0.0", 62 | "ts-loader": "^2.0.1", 63 | "typescript": "^2.2.1", 64 | "webpack": "2.2.0", 65 | "webpack-dev-server": "^1.14.1" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /__tests__/transaction-rollback-test.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Store, Actor, Action } from "../src/index"; 3 | import { Map } from 'immutable' 4 | 5 | type IMap = Map<string, any> 6 | 7 | class HelloActor extends Actor { 8 | defaultState() { 9 | return { text: 'hello' } 10 | } 11 | 12 | @Action('change') 13 | change(state: IMap, text) { 14 | throw new Error('change exception') 15 | return state.set('text', text) 16 | } 17 | } 18 | 19 | class LoadingActor extends Actor { 20 | defaultState() { 21 | return { loading: false } 22 | } 23 | 24 | @Action('loading:end') 25 | change(state: IMap) { 26 | return state.update('loading', loading => !loading) 27 | } 28 | } 29 | 30 | 31 | class AppStore extends Store { 32 | bindActor() { 33 | return [ 34 | new LoadingActor, 35 | new HelloActor 36 | ] 37 | } 38 | 39 | rollBack = () => { 40 | const isRollback = this.transaction(() => { 41 | this.dispatch('loading:end') 42 | this.dispatch('change', 'hello iflux2') 43 | }) 44 | 45 | expect(isRollback).toEqual(true) 46 | } 47 | 48 | customRollBack = () => { 49 | const currentState = this.state() 50 | const isRollback = this.transaction(() => { 51 | this.dispatch('loading:end') 52 | this.dispatch('change', 'hello iflux2') 53 | }, () => { 54 | expect(currentState != this.state()).toEqual(true) 55 | this._state = currentState 56 | }) 57 | 58 | expect(isRollback).toEqual(true) 59 | }; 60 | 61 | change = () => { 62 | this.transaction(() => { 63 | try { 64 | this.dispatch('loading:end') 65 | this.dispatch('change', 'hello iflux2') 66 | } catch (err) { } 67 | }) 68 | } 69 | } 70 | 71 | 72 | describe('test store transaction fail rollback', () => { 73 | it('test rollback', () => { 74 | const store = new AppStore({}) 75 | const state = store.state() 76 | store.rollBack() 77 | expect(store.state()).toEqual(state) 78 | }) 79 | 80 | it('test customRollBack', () => { 81 | const store = new AppStore({}) 82 | const state = store.state() 83 | store.customRollBack() 84 | expect(store.state()).toEqual(state) 85 | }) 86 | 87 | it('test without rollback', () => { 88 | const store = new AppStore({ debug: true }) 89 | const state = store.state() 90 | store.change() 91 | expect(store.state() == state).toEqual(false) 92 | }); 93 | }) -------------------------------------------------------------------------------- /examples/AwesomeApp/ios/AwesomeAppTests/AwesomeAppTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import <UIKit/UIKit.h> 11 | #import <XCTest/XCTest.h> 12 | 13 | #import <React/RCTLog.h> 14 | #import <React/RCTRootView.h> 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface AwesomeAppTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation AwesomeAppTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /examples/iflux2Native/ios/iflux2NativeTemplateTests/iflux2NativeTemplateTests.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import <UIKit/UIKit.h> 11 | #import <XCTest/XCTest.h> 12 | 13 | #import "RCTLog.h" 14 | #import "RCTRootView.h" 15 | 16 | #define TIMEOUT_SECONDS 600 17 | #define TEXT_TO_LOOK_FOR @"Welcome to React Native!" 18 | 19 | @interface iflux2NativeTemplateTests : XCTestCase 20 | 21 | @end 22 | 23 | @implementation iflux2NativeTemplateTests 24 | 25 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 26 | { 27 | if (test(view)) { 28 | return YES; 29 | } 30 | for (UIView *subview in [view subviews]) { 31 | if ([self findSubviewInView:subview matching:test]) { 32 | return YES; 33 | } 34 | } 35 | return NO; 36 | } 37 | 38 | - (void)testRendersWelcomeScreen 39 | { 40 | UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; 41 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 42 | BOOL foundElement = NO; 43 | 44 | __block NSString *redboxError = nil; 45 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 46 | if (level >= RCTLogLevelError) { 47 | redboxError = message; 48 | } 49 | }); 50 | 51 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 52 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 53 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 54 | 55 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 56 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 57 | return YES; 58 | } 59 | return NO; 60 | }]; 61 | } 62 | 63 | RCTSetLogFunction(RCTDefaultLogFunction); 64 | 65 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 66 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 67 | } 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /examples/todo/src/css/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "iflux2", 3 | "version": "1.4.5", 4 | "description": "Reactive state container (based on immutable) for React or ReactNative, inspired by mapreduce.", 5 | "main": "dist/index.js", 6 | "types": "./dist/index.d.ts", 7 | "scripts": { 8 | "test": "./node_modules/.bin/jest --verbose", 9 | "coverage": "./node_modules/.bin/jest --coverage", 10 | "compile": "./node_modules/.bin/tsc", 11 | "watch": "./node_modules/.bin/tsc -w", 12 | "build:core": "node_modules/.bin/babel --ignore build/contrib build --out-dir dist", 13 | "build:contrib": "node_modules/.bin/babel build/contrib --out-dir contrib", 14 | "type": "./scripts/type.sh", 15 | "clean": "rm -rf ./build && rm -rf contrib && rm -rf dist && rm -rf coverage", 16 | "release": "npm run compile && npm run build:core && npm run build:contrib && npm run type" 17 | }, 18 | "jest": { 19 | "testPathIgnorePatterns": [ 20 | "/node_modules/", 21 | "/examples/" 22 | ], 23 | "transform": { 24 | ".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js" 25 | }, 26 | "testRegex": "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", 27 | "moduleFileExtensions": [ 28 | "ts", 29 | "tsx", 30 | "js" 31 | ] 32 | }, 33 | "author": "hufeng", 34 | "license": "BSD", 35 | "dependencies": { 36 | "@types/jest": "^18.1.1", 37 | "@types/node": "^7.0.5", 38 | "events": "^1.1.0", 39 | "immutable": "^3.8.1", 40 | "object-assign": "^4.1.1" 41 | }, 42 | "files": [ 43 | "README.md", 44 | "dist", 45 | "contrib" 46 | ], 47 | "devDependencies": { 48 | "babel-cli": "^6.23.0", 49 | "babel-jest": "^19.0.0", 50 | "babel-plugin-syntax-object-rest-spread": "^6.13.0", 51 | "babel-plugin-transform-class-properties": "^6.23.0", 52 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 53 | "babel-plugin-transform-es2015-modules-commonjs": "^6.23.0", 54 | "babel-plugin-transform-flow-strip-types": "^6.22.0", 55 | "babel-plugin-transform-runtime": "^6.23.0", 56 | "babel-polyfill": "^6.23.0", 57 | "babel-preset-es2015": "^6.22.0", 58 | "babel-preset-react": "^6.23.0", 59 | "jest-cli": "^19.0.2", 60 | "react": "^15.4.2", 61 | "react-dom": "^15.4.2", 62 | "react-test-renderer": "^15.4.2", 63 | "ts-jest": "^19.0.0", 64 | "typescript": "^2.2.1" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "https://github.com/QianmiOpen/iflux2.git" 69 | }, 70 | "keywords": [ 71 | "Reactive", 72 | "state", 73 | "React", 74 | "ReactNative" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /src/contrib/atom.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 原子不可变数据容器 3 | */ 4 | import { fromJS, OrderedMap } from 'immutable'; 5 | import * as Cursor from 'immutable/contrib/cursor'; 6 | import { isArray, isStr, isFn } from './util'; 7 | 8 | export default class Atom { 9 | _atom: OrderedMap<string, any>; 10 | _callbacks: Array<Function>; 11 | 12 | /** 13 | * 初始化初始的数据结构 14 | */ 15 | constructor(record: Object) { 16 | this._callbacks = []; 17 | this._atom = fromJS(record); 18 | } 19 | 20 | /** 21 | * 获取值 22 | * 1. 如果path为空,就返回所有的值 23 | * 2. 如果path为字符串或者数组就按照immutable的path返回数据 24 | * 3. 其他返回空值 25 | * 26 | * @param path 27 | * @returns {*} 28 | */ 29 | value(path: string | Array<String>): any { 30 | let value = null; 31 | 32 | if (!path) { 33 | value = this._atom; 34 | } else if (isStr(path) || isArray(path)) { 35 | if (typeof path === 'string') { 36 | value = this._atom.get(path); 37 | } else { 38 | value = this._atom.getIn(path); 39 | } 40 | } 41 | 42 | return value; 43 | } 44 | 45 | /** 46 | * 获取cursor 47 | */ 48 | cursor(): Cursor.Cursor { 49 | return Cursor.from(this._atom, (newState, state, path) => { 50 | //校验数据是否过期 51 | if (state != this._atom) { 52 | console.log && console.log('attempted to alter expired data.'); 53 | } 54 | 55 | //校验有没有数据的变化 56 | if (newState === state) { 57 | return; 58 | } 59 | 60 | this._atom = newState; 61 | this._callbacks.forEach(cb => cb(newState, path)); 62 | }); 63 | } 64 | 65 | /** 66 | * 订阅 67 | */ 68 | subscribe(callback: Function): void { 69 | if (!callback || !isFn(callback)) { 70 | return; 71 | } 72 | 73 | //防止重复添加 74 | if (this._callbacks.indexOf(callback) == -1) { 75 | this._callbacks.push(callback); 76 | } 77 | } 78 | 79 | /** 80 | * 取消订阅 81 | */ 82 | unsubscribe(callback: Function): void { 83 | if (!callback || !isFn(callback)) { 84 | return; 85 | } 86 | 87 | const index = this._callbacks.indexOf(callback); 88 | if (index >= 0) { 89 | this._callbacks.splice(index, 1); 90 | } 91 | } 92 | 93 | /** 94 | * 打印出当前的内部数据状态 95 | */ 96 | pprint(): void { 97 | console.log(JSON.stringify(this._atom.toJS(), null, 2)); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /documents/gitbook/example.md: -------------------------------------------------------------------------------- 1 | # examples 2 | 3 | Counter demo 4 | 5 | 6 | ```js 7 | 8 | //counter-actor.js 9 | import {Actor, Action} from 'iflux2' 10 | import type {ActorState} from 'iflux2' 11 | 12 | export default class CounterActor extends Actor { 13 | //domain value 14 | defaultState() { 15 | return { 16 | count: 0 17 | } 18 | } 19 | 20 | @Action('increment') 21 | increment(state: ActorState) { 22 | return state.update('count', count => count + 1) 23 | } 24 | 25 | 26 | @Action('decrement') 27 | decrement(state: ActorState) { 28 | return state.update('count', count => count - 1) 29 | } 30 | } 31 | ``` 32 | 33 | ```js 34 | //store.js 35 | import {Store} from 'iflux2' 36 | import CounterActor from 'counter-actor' 37 | import type {StoreOptions} from 'iflux2' 38 | 39 | 40 | export default class AppStore extends Store { 41 | constructor(props: StoreOptions) { 42 | super(props) 43 | } 44 | 45 | bindActor() { 46 | return [ 47 | new CounterActor 48 | ] 49 | } 50 | 51 | increment = () => { 52 | this.dispatch('increment') 53 | }; 54 | 55 | decrement = () => { 56 | this.dispatch('decrement') 57 | }; 58 | } 59 | ``` 60 | 61 | ```js 62 | //ql.js 63 | import {QL} from 'iflux2' 64 | 65 | export const countQL = QL('counterQL', [ 66 | 'count', 67 | count => count 68 | ]) 69 | ``` 70 | 71 | 72 | ```js 73 | //counter-container.js 74 | import React, {Component} from 'react' 75 | import {Relax} from 'iflux2' 76 | import {countQL} from 'ql' 77 | const noop = () => {} 78 | 79 | @Relax 80 | export default class CounterContainer extends Component { 81 | static defaultProps = { 82 | count: countQL //注入bigQuery(countQL), 83 | //count: 0 //注入store.state.get('count') 84 | increment: noop, 85 | decrement: noop 86 | }; 87 | 88 | render() { 89 | const {count, increment, decrement} = this.props 90 | 91 | return ( 92 | <div> 93 | <a href='javascript:;' onClick={decrement}>-</a> 94 | <span>{count}</span> 95 | <a href='javascript:;' onClick={increment}>+</a> 96 | <div> 97 | ) 98 | } 99 | } 100 | ``` 101 | 102 | ```js 103 | //index.js 104 | import React, {Component} from 'react' 105 | import {StoreProvider} from 'iflux2' 106 | import AppStore from 'store' 107 | import CounterCounter from 'counter-container' 108 | 109 | 110 | @StoreProvider(AppStore) 111 | export default class CounterApp extends Component { 112 | render() { 113 | return ( 114 | <CounterCounter/> 115 | ) 116 | } 117 | } 118 | ``` 119 | 120 | ## more 121 | [examples](https://github.com/QianmiOpen/iflux2/tree/master/examples) 122 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native <methods>; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native <methods>; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # okhttp 54 | 55 | -keepattributes Signature 56 | -keepattributes *Annotation* 57 | -keep class okhttp3.** { *; } 58 | -keep interface okhttp3.** { *; } 59 | -dontwarn okhttp3.** 60 | 61 | # okio 62 | 63 | -keep class sun.misc.Unsafe { *; } 64 | -dontwarn java.nio.file.* 65 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 66 | -dontwarn okio.** 67 | -------------------------------------------------------------------------------- /examples/AwesomeApp/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /examples/iflux2Native/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | --------------------------------------------------------------------------------