常用几个正则

表单验证经常需要用到正则,不知道规则纵使熟练REG也是无济于事,下面整理几个常用正则

  • 手机号码验证

    function isPhone(tel){

      var phone= /^((\+?[0-9]{1,4})|(\(\+86\)))?(13[0-9]|14[57]|15[012356789]|17[0678]|18[0-9])\d{8}$/;
      if (phone.test(tel)){
          return true;
      }else{
          return false;
      }
    }
    
  • 邮箱验证

    function isEmail(email){

      var isEmail= /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/;
      if (isEmail.test(email)){
          return true;
      }else{
          return false;
      }
    }
    
  • 身份证验证

    function isID(id){

      var reg15 = /^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$/;
      var _reg15 = /^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$/;
      var reg18 = /^[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}[0-9Xx]$/;
      var _reg18 = /^[1-9][0-9]{5}19[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}[0-9Xx]$/;
      if (reg15.test(id)||_reg15.test(id)||reg18.test(id)||_reg18.test(id)){
          return true;
      }else{
          return false;
      }
    }
    

配置相关-eslint

ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具,它的目标是保证代码的一致性和避免错误。在许多方面,它和 JSLint、JSHint 相似,除了少数的例外:

ESlint的安装使用还是推荐用编辑器插件的方式,自动检测,更不要开多余的命令行,详情参见各种编辑器配置

规则定义:

ESLint 支持几种格式的配置文件,如果同一个目录下有多个配置文件,ESLint 只会使用一个。优先级顺序如下:

  1. JavaScript - 使用 .eslintrc.js 然后输出一个配置对象。
  2. YAML - 使用 .eslintrc.yaml 或 .eslintrc.yml 去定义配置的结构。
  3. JSON -使用 .eslintrc.json 去定义配置的结构,ESLint 的 JSON 文件允许 JavaScript 风格的注释。
  4. Deprecated -使用 .eslintrc,可以使 JSON 也可以是 YAML。
  5. package.json - 在 package.json 里创建一个 eslintConfig属性,在那里定义你的配置。

规则释义:

{
    // 环境定义了预定义的全局变量。
    "env": {
        //环境定义了预定义的全局变量。更多在官网查看
        "browser":true,
        "node":true,
        "commonjs":true,
        "amd":true,
        "es6":true,
        "mocha":true
    },
    // JavaScript 语言选项
    "parserOptions": {
        // ECMAScript 版本
        "ecmaVersion":6,
        "sourceType":"script",//module
        // 想使用的额外的语言特性:
        "ecmaFeatures": {
            // 允许在全局作用域下使用 return 语句
            "globalReturn":true,
            // impliedStric
            "impliedStrict":true,
            // 启用 JSX
            "jsx":true
        }
     },
    /**
     * "off" 或 0 - 关闭规则
     * "warn" 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出),
     * "error" 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
     */
    "rules": {
        // 禁止条件表达式中出现赋值操作符
        "no-cond-assign":2,
        // 禁用 console
        "no-console":0,
        // 禁止在条件中使用常量表达式
        // if (false) {
        // doSomethingUnfinished();
        // } //cuowu
        "no-constant-condition":2,
        // 禁止在正则表达式中使用控制字符 :new RegExp("\x1f")
        "no-control-regex":2,
        // 数组和对象键值对最后一个逗号, never参数:不能带末尾的逗号, always参数:必须带末尾的逗号,
        // always-multiline:多行模式必须带逗号,单行模式不能带逗号
        "comma-dangle": [1,"always-multiline"],
        // 禁用 debugger
        "no-debugger":2,
        // 禁止 function 定义中出现重名参数
        "no-dupe-args":2,
        // 禁止对象字面量中出现重复的 key
        "no-dupe-keys":2,
        // 禁止重复的 case 标签
        "no-duplicate-case":2,
        // 禁止空语句块
        "no-empty":2,
        // 禁止在正则表达式中使用空字符集 (/^abc[]/)
        "no-empty-character-class":2,
        // 禁止对 catch 子句的参数重新赋值
        "no-ex-assign":2,
        // 禁止不必要的布尔转换
        "no-extra-boolean-cast":2,
        // 禁止不必要的括号 //(a * b) + c;//报错
        "no-extra-parens":0,
        // 禁止不必要的分号
        "no-extra-semi":2,
        // 禁止对 function 声明重新赋值
        "no-func-assign":2,
        // 禁止在嵌套的块中出现 function 或 var 声明
        "no-inner-declarations": [2,"functions"],
        // 禁止 RegExp 构造函数中无效的正则表达式字符串
        "no-invalid-regexp":2,
        // 禁止在字符串和注释之外不规则的空白
        "no-irregular-whitespace":2,
        // 禁止在 in 表达式中出现否定的左操作数
        "no-negated-in-lhs":2,
        // 禁止把全局对象 (Math 和 JSON) 作为函数调用 错误:var math = Math();
        "no-obj-calls":2,
        // 禁止直接使用 Object.prototypes 的内置属性
        "no-prototype-builtins":0,
        // 禁止正则表达式字面量中出现多个空格
        "no-regex-spaces":2,
        // 禁用稀疏数组
        "no-sparse-arrays":2,
        // 禁止出现令人困惑的多行表达式
        "no-unexpected-multiline":2,
        // 禁止在return、throw、continue 和 break语句之后出现不可达代码
        /*
         function foo() {
         return true;
         console.log("done");
         }//错误
         */
        "no-unreachable":2,
        // 要求使用 isNaN() 检查 NaN
        "use-isnan":2,
        // 强制使用有效的 JSDoc 注释
        "valid-jsdoc":1,
        // 强制 typeof 表达式与有效的字符串进行比较
        // typeof foo === "undefimed" 错误
        "valid-typeof":2,
        // 定义对象的set存取器属性时,强制定义get
        "accessor-pairs":2,
        // 强制数组方法的回调函数中有 return 语句
        "array-callback-return":0,
        // 强制把变量的使用限制在其定义的作用域范围内
        "block-scoped-var":0,
        // 限制圈复杂度,也就是类似if else能连续接多少个
        "complexity": [2,9],
        // 要求 return 语句要么总是指定返回的值,要么不指定
        "consistent-return":0,
        // 强制所有控制语句使用一致的括号风格
        "curly": [2,"all"],
        // switch 语句强制 default 分支,也可添加 // no default 注释取消此次警告
        "default-case":2,
        // 强制object.key 中 . 的位置,参数:
        // property,'.'号应与属性在同一行
        // object, '.' 号应与对象名在同一行
        "dot-location": [2,"property"],
        // 强制使用.号取属性
        // 参数: allowKeywords:true 使用保留字做属性名时,只能使用.方式取属性
        // false 使用保留字做属性名时, 只能使用[]方式取属性 e.g [2, {"allowKeywords": false}]
        // allowPattern: 当属性名匹配提供的正则表达式时,允许使用[]方式取值,否则只能用.号取值 e.g [2, {"allowPattern": "^[a-z]+(_[a-z]+)+$"}]
        "dot-notation": [2, {"allowKeywords":false}],
        // 使用 === 替代 == allow-null允许null和undefined==
        "eqeqeq": [2,"allow-null"],
        // 要求 for-in 循环中有一个 if 语句
        "guard-for-in":2,
        // 禁用 alert、confirm 和 prompt
        "no-alert":0,
        // 禁用 arguments.caller 或 arguments.callee
        "no-caller":2,
        // 不允许在 case 子句中使用词法声明
        "no-case-declarations":2,
        // 禁止除法操作符显式的出现在正则表达式开始的位置
        "no-div-regex":2,
        // 禁止 if 语句中有 return 之后有 else
        "no-else-return":0,
        // 禁止出现空函数.如果一个函数包含了一条注释,它将不会被认为有问题。
        "no-empty-function":2,
        // 禁止使用空解构模式no-empty-pattern
        "no-empty-pattern":2,
        // 禁止在没有类型检查操作符的情况下与 null 进行比较
        "no-eq-null":1,
        // 禁用 eval()
        "no-eval":2,
        // 禁止扩展原生类型
        "no-extend-native":2,
        // 禁止不必要的 .bind() 调用
        "no-extra-bind":2,
        // 禁用不必要的标签
        "no-extra-label:":0,
        // 禁止 case 语句落空
        "no-fallthrough":2,
        // 禁止数字字面量中使用前导和末尾小数点
        "no-floating-decimal":2,
        // 禁止使用短符号进行类型转换(!!fOO)
        "no-implicit-coercion":0,
        // 禁止在全局范围内使用 var 和命名的 function 声明
        "no-implicit-globals":1,
        // 禁止使用类似 eval() 的方法
        "no-implied-eval":2,
        // 禁止 this 关键字出现在类和类对象之外
        "no-invalid-this":0,
        // 禁用 __iterator__ 属性
        "no-iterator":2,
        // 禁用标签语句
        "no-labels":2,
        // 禁用不必要的嵌套块
        "no-lone-blocks":2,
        // 禁止在循环中出现 function 声明和表达式
        "no-loop-func":1,
        // 禁用魔术数字(3.14什么的用常量代替)
        "no-magic-numbers":[1,{"ignore": [0,-1,1] }],
        // 禁止使用多个空格
        "no-multi-spaces":2,
        // 禁止使用多行字符串,在 JavaScript 中,可以在新行之前使用斜线创建多行字符串
        "no-multi-str":2,
        // 禁止对原生对象赋值
        "no-native-reassign":2,
        // 禁止在非赋值或条件语句中使用 new 操作符
        "no-new":2,
        // 禁止对 Function 对象使用 new 操作符
        "no-new-func":0,
        // 禁止对 String,Number 和 Boolean 使用 new 操作符
        "no-new-wrappers":2,
        // 禁用八进制字面量
        "no-octal":2,
        // 禁止在字符串中使用八进制转义序列
        "no-octal-escape":2,
        // 不允许对 function 的参数进行重新赋值
        "no-param-reassign":0,
        // 禁用 __proto__ 属性
        "no-proto":2,
        // 禁止使用 var 多次声明同一变量
        "no-redeclare":2,
        // 禁用指定的通过 require 加载的模块
        "no-return-assign":0,
        // 禁止使用 javascript: url
        "no-script-url":0,
        // 禁止自我赋值
        "no-self-assign":2,
        // 禁止自身比较
        "no-self-compare":2,
        // 禁用逗号操作符
        "no-sequences":2,
        // 禁止抛出非异常字面量
        "no-throw-literal":2,
        // 禁用一成不变的循环条件
        "no-unmodified-loop-condition":2,
        // 禁止出现未使用过的表达式
        "no-unused-expressions":0,
        // 禁用未使用过的标签
        "no-unused-labels":2,
        // 禁止不必要的 .call() 和 .apply()
        "no-useless-call":2,
        // 禁止不必要的字符串字面量或模板字面量的连接
        "no-useless-concat":2,
        // 禁用不必要的转义字符
        "no-useless-escape":0,
        // 禁用 void 操作符
        "no-void":0,
        // 禁止在注释中使用特定的警告术语
        "no-warning-comments":0,
        // 禁用 with 语句
        "no-with":2,
        // 强制在parseInt()使用基数参数
        "radix":2,
        // 要求所有的 var 声明出现在它们所在的作用域顶部
        "vars-on-top":0,
        // 要求 IIFE 使用括号括起来
        "wrap-iife": [2,"any"],
        // 要求或禁止 “Yoda” 条件
        "yoda": [2,"never"],
        // 要求或禁止使用严格模式指令
        "strict":0,
        //////////////
        // 变量声明 //
        //////////////

        // 要求或禁止 var 声明中的初始化(初值)
        "init-declarations":0,
        // 不允许 catch 子句的参数与外层作用域中的变量同名
        "no-catch-shadow":0,
        // 禁止删除变量
        "no-delete-var":2,
        // 不允许标签与变量同名
        "no-label-var":2,
        // 禁用特定的全局变量
        "no-restricted-globals":0,
        // 禁止 var 声明 与外层作用域的变量同名
        "no-shadow":0,
        // 禁止覆盖受限制的标识符
        "no-shadow-restricted-names":2,
        // 禁用未声明的变量,除非它们在 /*global */ 注释中被提到
        "no-undef":2,
        // 禁止将变量初始化为 undefined
        "no-undef-init":2,
        // 禁止将 undefined 作为标识符
        "no-undefined":0,
        // 禁止出现未使用过的变量
        "no-unused-vars": [2, {"vars":"all","args":"none"}],
        // 不允许在变量定义之前使用它们
        "no-use-before-define":0,

        //////////////////////////
        // Node.js and CommonJS //
        //////////////////////////

        // require return statements after callbacks
        "callback-return":0,
        // 要求 require() 出现在顶层模块作用域中
        "global-require":1,
        // 要求回调函数中有容错处理
        "handle-callback-err": [2,"^(err|error)$"],
        // 禁止混合常规 var 声明和 require 调用
        "no-mixed-requires":0,
        // 禁止调用 require 时使用 new 操作符
        "no-new-require":2,
        // 禁止对 __dirname 和 __filename进行字符串连接
        "no-path-concat":0,
        // 禁用 process.env
        "no-process-env":0,
        // 禁用 process.exit()
        "no-process-exit":0,
        // 禁用同步方法
        "no-sync":0,

        //////////////
        // 风格指南 //
        //////////////

        // 指定数组的元素之间要以空格隔开(, 后面), never参数:[ 之前和 ] 之后不能带空格,always参数:[ 之前和 ] 之后必须带空格
        "array-bracket-spacing": [2,"never"],
        // 禁止或强制在单行代码块中使用空格(禁用)
        "block-spacing":[1,"never"],
        //强制使用一致的缩进 第二个参数为 "tab" 时,会使用tab,
        // if while function 后面的{必须与if在同一行,java风格。
        "brace-style": [2,"1tbs", {"allowSingleLine":true}],
        // 双峰驼命名格式
        "camelcase":2,
        // 控制逗号前后的空格
        "comma-spacing": [2, {"before":false,"after":true}],
        // 控制逗号在行尾出现还是在行首出现 (默认行尾)
        // http://eslint.org/docs/rules/comma-style
        "comma-style": [2,"last"],
        //"SwitchCase" (默认:0) 强制 switch 语句中的 case 子句的缩进水平
        // 以方括号取对象属性时,[ 后面和 ] 前面是否需要空格, 可选参数 never, always
        "computed-property-spacing": [2,"never"],
        // 用于指统一在回调函数中指向this的变量名,箭头函数中的this已经可以指向外层调用者,应该没卵用了
        // e.g [0,"that"] 指定只能 var that = this. that不能指向其他任何值,this也不能赋值给that以外的其他值
        "consistent-this": [1,"that"],
        // 强制使用命名的 function 表达式
        "func-names":0,
        // 文件末尾强制换行
        "eol-last":2,
        "indent": [2,4, {"SwitchCase":1}],
        // 强制在对象字面量的属性中键和值之间使用一致的间距
        "key-spacing": [2, {"beforeColon":false,"afterColon":true}],
        // 强制使用一致的换行风格
        "linebreak-style": [1,"unix"],
        // 要求在注释周围有空行 ( 要求在块级注释之前有一空行)
        "lines-around-comment": [1,{"beforeBlockComment":true}],
        // 强制一致地使用函数声明或函数表达式,方法定义风格,参数:
        // declaration: 强制使用方法声明的方式,function f(){} e.g [2, "declaration"]
        // expression:强制使用方法表达式的方式,var f = function() {} e.g [2, "expression"]
        // allowArrowFunctions: declaration风格中允许箭头函数。 e.g [2, "declaration", { "allowArrowFunctions": true }]
        "func-style":0,
        // 强制回调函数最大嵌套深度 5层
        "max-nested-callbacks": [1,5],
        // 禁止使用指定的标识符
        "id-blacklist":0,
        // 强制标识符的最新和最大长度
        "id-length":0,
        // 要求标识符匹配一个指定的正则表达式
        "id-match":0,
        // 强制在 JSX 属性中一致地使用双引号或单引号
        "jsx-quotes":0,
        // 强制在关键字前后使用一致的空格 (前后腰需要)
        "keyword-spacing":2,
        // 强制一行的最大长度
        "max-len":[1,200],
        // 强制最大行数
        "max-lines":0,
        // 强制 function 定义中最多允许的参数数量
        "max-params":[1,7],
        // 强制 function 块最多允许的的语句数量
        "max-statements":[1,200],
        // 强制每一行中所允许的最大语句数量
        "max-statements-per-line":0,
        // 要求构造函数首字母大写 (要求调用 new 操作符时有首字母大小的函数,允许调用首字母大写的函数时没有 new 操作符。)
        "new-cap": [2, {"newIsCap":true,"capIsNew":false}],
        // 要求调用无参构造函数时有圆括号
        "new-parens":2,
        // 要求或禁止 var 声明语句后有一行空行
        "newline-after-var":0,
        // 禁止使用 Array 构造函数
        "no-array-constructor":2,
        // 禁用按位运算符
        "no-bitwise":0,
        // 要求 return 语句之前有一空行
        "newline-before-return":0,
        // 要求方法链中每个调用都有一个换行符
        "newline-per-chained-call":1,
        // 禁用 continue 语句
        "no-continue":0,
        // 禁止在代码行后使用内联注释
        "no-inline-comments":0,
        // 禁止 if 作为唯一的语句出现在 else 语句中
        "no-lonely-if":0,
        // 禁止混合使用不同的操作符
        "no-mixed-operators":0,
        // 不允许空格和 tab 混合缩进
        "no-mixed-spaces-and-tabs":2,
        // 不允许多个空行
        "no-multiple-empty-lines": [2, {"max":2}],
        // 不允许否定的表达式
        "no-negated-condition":0,
        // 不允许使用嵌套的三元表达式
        "no-nested-ternary":0,
        // 禁止使用 Object 的构造函数
        "no-new-object":2,
        // 禁止使用一元操作符 ++ 和 --
        "no-plusplus":0,
        // 禁止使用特定的语法
        "no-restricted-syntax":0,
        // 禁止 function 标识符和括号之间出现空格
        "no-spaced-func":2,
        // 不允许使用三元操作符
        "no-ternary":0,
        // 禁用行尾空格
        "no-trailing-spaces":2,
        // 禁止标识符中有悬空下划线_bar
        "no-underscore-dangle":0,
        // 禁止可以在有更简单的可替代的表达式时使用三元操作符
        "no-unneeded-ternary":2,
        // 禁止属性前有空白
        "no-whitespace-before-property":0,
        // 强制花括号内换行符的一致性
        "object-curly-newline":0,
        // 强制在花括号中使用一致的空格
        "object-curly-spacing":0,
        // 强制将对象的属性放在不同的行上
        "object-property-newline":0,
        // 强制函数中的变量要么一起声明要么分开声明
        "one-var": [2, {"initialized":"never"}],
        // 要求或禁止在 var 声明周围换行
        "one-var-declaration-per-line":0,
        // 要求或禁止在可能的情况下要求使用简化的赋值操作符
        "operator-assignment":0,
        // 强制操作符使用一致的换行符
        "operator-linebreak": [2,"after", {"overrides": {"?":"before",":":"before"} }],
        // 要求或禁止块内填充
        "padded-blocks":0,
        // 要求对象字面量属性名称用引号括起来
        "quote-props":0,
        // 强制使用一致的反勾号、双引号或单引号
        "quotes": [2,"single","avoid-escape"],
        // 要求使用 JSDoc 注释
        "require-jsdoc":1,
        // 要求或禁止使用分号而不是 ASI(这个才是控制行尾部分号的,)
        "semi": [2,"always"],
        // 强制分号之前和之后使用一致的空格
        "semi-spacing":0,
        // 要求同一个声明块中的变量按顺序排列
        "sort-vars":0,
        // 强制在块之前使用一致的空格
        "space-before-blocks": [2,"always"],
        // 强制在 function的左括号之前使用一致的空格
        "space-before-function-paren": [2,"always"],
        // 强制在圆括号内使用一致的空格
        "space-in-parens": [2,"never"],
        // 要求操作符周围有空格
        "space-infix-ops":2,
        // 强制在一元操作符前后使用一致的空格
        "space-unary-ops": [2, {"words":true,"nonwords":false}],
        // 强制在注释中 // 或 /* 使用一致的空格
        "spaced-comment": [2,"always", {"markers": ["global","globals","eslint","eslint-disable","*package","!"] }],
        // 要求或禁止 Unicode BOM
        "unicode-bom":0,
        // 要求正则表达式被括号括起来
        "wrap-regex":0,

        //////////////
        // ES6.相关 //
        //////////////

        // 要求箭头函数体使用大括号
        "arrow-body-style":2,
        // 要求箭头函数的参数使用圆括号
        "arrow-parens":2,
        "arrow-spacing":[2,{"before":true,"after":true}],
        // 强制在子类构造函数中用super()调用父类构造函数,TypeScrip的编译器也会提示
        "constructor-super":0,
        // 强制 generator 函数中 * 号周围使用一致的空格
        "generator-star-spacing": [2, {"before":true,"after":true}],
        // 禁止修改类声明的变量
        "no-class-assign":2,
        // 不允许箭头功能,在那里他们可以混淆的比较
        "no-confusing-arrow":0,
        // 禁止修改 const 声明的变量
        "no-const-assign":2,
        // 禁止类成员中出现重复的名称
        "no-dupe-class-members":2,
        // 不允许复制模块的进口
        "no-duplicate-imports":0,
        // 禁止 Symbol 的构造函数
        "no-new-symbol":2,
        // 允许指定模块加载时的进口
        "no-restricted-imports":0,
        // 禁止在构造函数中,在调用 super() 之前使用 this 或 super
        "no-this-before-super":2,
        // 禁止不必要的计算性能键对象的文字
        "no-useless-computed-key":0,
        // 要求使用 let 或 const 而不是 var
        "no-var":0,
        // 要求或禁止对象字面量中方法和属性使用简写语法
        "object-shorthand":0,
        // 要求使用箭头函数作为回调
        "prefer-arrow-callback":0,
        // 要求使用 const 声明那些声明后不再被修改的变量
        "prefer-const":0,
        // 要求在合适的地方使用 Reflect 方法
        "prefer-reflect":0,
        // 要求使用扩展运算符而非 .apply()
        "prefer-spread":0,
        // 要求使用模板字面量而非字符串连接
        "prefer-template":0,
        // Suggest using the rest parameters instead of arguments
        "prefer-rest-params":0,
        // 要求generator 函数内有 yield
        "require-yield":0,
        // enforce spacing between rest and spread operators and their expressions
        "rest-spread-spacing":0,
        // 强制模块内的 import 排序
        "sort-imports":0,
        // 要求或禁止模板字符串中的嵌入表达式周围空格的使用
        "template-curly-spacing":1,
        // 强制在 yield* 表达式中 * 周围使用空格
        "yield-star-spacing":2
     }
}

saga-begining

redux-saga 是一个用于管理 Redux 应用异步操作的中间件(又称异步 action)。 redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。

这意味着应用的逻辑会存在两个地方:

  • Reducers 负责处理 action 的 state 更新。
  • Sagas 负责协调那些复杂或异步的操作。
  • 此篇文章重在完成saga的基本应用,其中会用到一些其他很有用的插件,saga详细说明请参考文档Redux-saga中文文档
  • 文章项目源码戳github

这里我们用saga完整一个Counter的使用

index.js—挂在到项目元素root上

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import createStore from './store/configureStore'
import App from './App';

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

上面index.js用到的creatrStore.js

import {createLogger} from 'redux-logger';
// const createLogger = require('redux-logger');
import createSagaMiddleware from 'redux-saga';
import {createStore, applyMiddleware } from 'redux';
import rootReducer from '../redux'
import rootSaga from '../saga'

const middlewares = [];
// 创建中间件saga
const sagaMiddleware = createSagaMiddleware();
middlewares.push(sagaMiddleware)

if (process.env.NODE_ENV === 'development') {
    //创建中间件logger
    const logger = createLogger();
    middlewares.push(logger);
}

export default function configureStore(initialState) {
   const store = createStore(rootReducer, initialState, applyMiddleware(...middlewares));
   sagaMiddleware.run(rootSaga) //saga中间键挂在到store后需要run一下 所有的saga文件
   return store;
}

createStore用到的rootRedux

import {combineReducers} from 'redux';
const rootReducer = combineReducers({
  counterReducer1: require('./counterReducer1').reducer,
  counterReducer2: require('./counterReducer2').reducer,
})
export default rootReducer

createStore用到的rootSaga

import { takeLatest } from 'redux-saga/effects'
/* ------------- Types ------------- */
import { CounterTypes1 } from '../redux/counterReducer1'
import { CounterTypes2 } from '../redux/counterReducer2'
/* ------------- Sagas ------------- */
import { counterPlus1, counterMinus1 } from './counterSaga1'
import { counterPlus2, counterMinus2 } from './counterSaga2'
/* ------------- Connect Types To Sagas ------------- */
export default function * rootSaga () {
  yield [
    takeLatest(CounterTypes1.PLUS_NUM_SAGA, counterPlus1),
    takeLatest(CounterTypes1.MINUS_NUM_SAGA, counterMinus1),
    takeLatest(CounterTypes2.PLUS_NUM_SAGA2, counterPlus2),
    takeLatest(CounterTypes2.MINUS_NUM_SAGA2, counterMinus2),
  ]
}

rootRedux 引入的counterReducer1

import { createReducer, createActions } from 'reduxsauce'
import Immutable from 'seamless-immutable'
/* ------------- Types and Action Creators ------------- */
const { Types, Creators } = createActions({
  plusNum: ['text'], //type名驼峰,生成的是全大写PLUS_NUM
  minusNum: null,
  plusNumSaga: ['text'],
  minusNumSaga: ['text'],
})

export const CounterTypes1 = Types //这里导出是给saga监听使用
export default Creators
//导出action生成器 Creators,
//Creators.plusNum =(text) => {type:'PLUS_NUM' ,text:text}
/* ------------- Initial State ------------- */
export const INITIAL_STATE = Immutable({
  value:0
})
/* ------------- Reducers ------------- */
export const plus = (state,action) => {
  const { text } = action
  return state.merge({ value:state.value+text })
}
export const minus = (state,action) => {
  return state.merge({ value:state.value-1 })
}
/* ------------- Hookup Reducers To Types ------------- */
export const reducer = createReducer(INITIAL_STATE, {
  [Types.PLUS_NUM]: plus,
  [Types.MINUS_NUM]: minus
  //这里reducer监听Types是PLUS_NUM的action,执行对应的plus放法
})
// reducer生成器,生成对应type的reducer

rootSaga引入的counterSaga1

import { put,select,call } from 'redux-saga/effects'
import CounterActions1 from '../redux/counterReducer1'

// attempts to get state
// const getState = (state) => state.login

// let API={
//     request:(text)=>{
//         return fetch(...)
//     }
// }

export function * counterPlus1 (action) {
    // const data = yield select(selectState) //通过select可以获取全局state

  // const check = yield call(API.request, text) //如果有异步请求,用call调用
  // if(check.ok == 'ok'){
  //     yield put(CounterActions1.xxx(xxx, xxx))
  // }
   const {text} = action
   alert(text)
   yield put(CounterActions1.plusNum(2))

}

export function * counterMinus1 (action) {
   const {text} = action
   alert(text)
   yield put(CounterActions1.minusNum())
}

谨记

  • sagareducer都会监听action监听的顺序是先reducersaga,所以如果reducersaga都监听了相同的action,那么saga逻辑执行在reducer改变后的状态基础上,尽量不要用sagareducer监听相同的reducer
  • 相同的action内容,不管是谁触发的,所有监听此actionreduceraction都会触发,所以请区分好action的触发及监听操作
  • thunk一样(dispatch,getState) 第二个参数会将全局的state函数当参数传递进来,sagaselect(()=>fn(state))也可以调用全局state

imgClipUpload

很多情况下用户上传的图片都需要经过裁剪,最常见的就是头像了。HTML5 的出现让我们可以更方便的实现这一需求。

直接在gitHub查看imgClipUpload🚘

✨HTML:

<!-- 带Id的标签是必须要的 -->
<canvas id="canvas" ></canvas> //画布
<div class="file rake">
    <input type="file" id="file"/>上传图片
</div>
<div id="rotateR" class="rake">旋转</div>
<div class="cancle rake">取消</div>
<div id="save" class="rake">截图</div>
<div class="upload rake">上传</div>
<img src="" alt="" id="img"/> //截图或保存的图片

✨JS:

  • 按钮移入移除动画效果

    $('.rake').mouseenter(function(){
        $(this).addClass('rubberBand animated infinite')
    })
    .mouseleave(function(){
        $(this).removeClass('rubberBand animated infinite')
    })
    
  • 创建截图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var cut = new ImgClip({
    canvas : 'canvas', // canvas id
    width:$(window).width(), //canvas 宽度
    height:$(window).height(), //canvas 高度
    fileObj : 'file', // file id
    cutBtn : 'save', // cut btn id
    resultObj : 'img', // result img id
    rotateR : 'rotateR',
    cutScale : 3/4, // 高:宽
    cutW : '300' // '数字'或者'winW'关键字,裁切宽度,随屏幕宽或自己设置
    });
  • 点击选择本地图片预览

    1
    2
    3
    4
    5
    $('.file').on('change','#file',function(){
    $('#canvas').css('display','block')
    $('#img').css('display','none')
    $(this)[0].value=''
    })
  • 取消预览

    $('.cancle').click(function(){
      $('#canvas').css('display','none')
      $('#img').css('display','block')
    })
    

  • 保存截图

    1
    2
    3
    4
    $('#save').click(function(){
    $('#canvas').css('display','none')
    $('#img').css('display','block')
    })
  • 点击上传图片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    $('.upload').click(function(){
    var url = $('#img').attr('src')
    var file = dataURItoBlob(url)
    var fd=new FormData();
    fd.append('file',file);
    //如果需要多图上传只需要把对象添加进数组就可以了,向这样:fd.append('file[]',file);
    $.ajax({
    url:"php/upload.php",
    type:"POST",
    data:fd,
    processData: false,
    contentType: false,
    success:function(data){
    console.log(data)
    }
    });
    })
  • base64转Blob

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function dataURItoBlob (base64Data) {
    var byteString;
    if (base64Data.split(',')[0].indexOf('base64') >= 0)
    byteString = atob(base64Data.split(',')[1]);
    else
    byteString = unescape(base64Data.split(',')[1]);
    var type = base64Data.split(',')[0].split(':')[1].split(';')[0];
    var ia = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
    }
    // canvas.toDataURL 返回的默认格式是 image/png
    return new Blob([ia], {type: type});
    }

✨php:

<?php
  header('Access-Control-Allow-Origin:*');
  //处理文件
  if(!file_exists('./img')){
      mkdir('./img');
  }
  $file = $_FILES['file']['tmp_name'];  //获取文件路径
  $newName = $_FILES['file']['name']; //获取文件的名字
  $res = move_uploaded_file($file,"./img/{$newName}"); //原名上传到指定文件夹
  print_r($_FILES)
?>

😝😝😝😝😝😝😝😝😝😝😝😝😝😝😝😝😝😝

配置相关—package.json

name

在package.json中_最_重要的就是name和version字段。他们都是必须的,如果没有就无法install。name和version一起组成的标识在假设中是唯一的。改变包应该同时改变version。

name是这个东西的名字。注意:

  • 不要把node或者js放在名字中。因为你写了package.json它就被假定成为了js,不过你可以用”engine”字段指定一个引擎(见后文)。
  • 这个名字会作为在URL的一部分、命令行的参数或者文件夹的名字。任何non-url-safe的字符都是不能用的。
  • 这个名字可能会作为参数被传入require(),所以它应该比较短,但也要意义清晰。
  • 在你爱上你的名字之前,你可能要去npm registry查看一下这个名字是否已经被使用了。 http://registry.npmjs.org/

version

在package.json中_最_重要的就是name和version字段。他们都是必须的,如果没有就无法install。name和version一起组成的标识在假设中是唯一的。改变包应该同时改变version。

version必须能被 node-semver解析,它被包在npm的依赖中。(要自己用可以执行npm install semver

更多可用的“数字”或者“范围”见semver(7).

description

Put a description in it. It’s a string. This helps people discover your package, as it’s listed in npm search.

keywords

放简介,字符串。方便屌丝们在 npm search中搜索。

homepage

项目官网的url。

bugs

你项目的提交问题的url和(或)邮件地址。这对遇到问题的屌丝很有帮助。

差不多长这样:

1
2
3
{ "url" : "http://github.com/owner/project/issues"
, "email" : "project@hostname.com"
}

你可以指定一个或者指定两个。如果你只想提供一个url,那就不用对象了,字符串就行。

如果提供了url,它会被npm bugs命令使用。

license

你应该要指定一个许可证,让人知道使用的权利和限制的。

最简单的方法是,假如你用一个像BSD或者MIT这样通用的许可证,就只需要指定一个许可证的名字,像这样:

1
{ "license" : "BSD" }

people fields: author, contributors

author是一个人。contributors是一堆人的数组。person是一个有name字段,可选的有url、email字段的对象,像这样:

1
2
3
4
{ "name" : "Barney Rubble"
, "email" : "b@rubble.com"
, "url" : "http://barnyrubble.tumblr.com/"
}

files

files是一个包含项目中的文件的数组。如果命名了一个文件夹,那也会包含文件夹中的文件。(除非被其他条件忽略了)

你也可以提供一个.npmignore文件,即,使被包含在files字段中得文件被留下。其实就像.gitignore一样。

main

main字段配置一个文件名指向模块的入口程序。如果你包的名字叫foo,然后用户require(“foo”),main配置的模块的exports对象会被返回。

这应该是一个相对于根目录的文件路径。

对于大多数模块,它是非常有意义的,其他的都没啥。

bin

很多包都有一个或多个可执行的文件希望被放到PATH中。npm让妈妈再也不用担心了(实际上,就是这个功能让npm可执行的)。

要用这个功能,给package.json中的bin字段一个命令名到文件位置的map。初始化的时候npm会将他链接到prefix/bin(全局初始化)或者./node_modules/.bin/(本地初始化)。

比如,npm有:

1
{ "bin" : { "npm" : "./cli.js" } }

所以,当你初始化npm,它会创建一个符号链接到cli.js脚本到/usr/local/bin/npm

如果你只有一个可执行文件,并且名字和包名一样。那么你可以只用一个字符串,比如:

1
2
3
{ "name": "my-program"
, "version": "1.2.5"
, "bin": "./path/to/program" }

结果和这个一样:

1
2
3
{ "name": "my-program"
, "version": "1.2.5"
, "bin" : { "my-program" : "./path/to/program" } }

repository

指定你的代码存放的地方。这个对希望贡献的人有帮助。如果git仓库在github上,那么npm docs命令能找到你。

这样做:

1
2
3
4
5
6
7
8
9
"repository" :
{ "type" : "git"
, "url" : "http://github.com/isaacs/npm.git"
}

"repository" :
{ "type" : "svn"
, "url" : "http://v8.googlecode.com/svn/trunk/"
}

URL应该是公开的(即便是只读的)能直接被未经过修改的版本控制程序处理的url。不应该是一个html的项目页面。因为它是给计算机看的。

scripts

“scripts”是一个由脚本命令组成的hash对象,他们在包不同的生命周期中被执行。key是生命周期事件,value是要运行的命令。其中starttest可以简写像这样npn start 而不用npm run start

参见 npm-scripts(7)

config

“config” hash可以用来配置用于包脚本中的跨版本参数。在实例中,如果一个包有下面的配置:

1
2
{ "name" : "foo"
, "config" : { "port" : "8080" } }

然后有一个“start”命令引用了npm_package_config_port环境变量,用户可以通过npm config set foo:port 8001来重写他。

参见 npm-config(7)npm-scripts(7)

dependencies

项目运行需要依赖的插件

请不要将测试或过渡性的依赖放在dependencies中。见下文的devDependencies

详见semver(7).

  • version 必须完全和version一致
  • >version 必须比version
  • >=version 同上
  • <version 同上
  • <=version 同上
  • ~version 约等于,见semver(7)
  • ^version 相同大版本的最新版本
  • 1.2.x 1.2.0, 1.2.1, 等,但不包括1.3.0
  • http://... 见下文’依赖URL’
  • * 所有
  • "" 空,同*
  • version1 - version2>=version1 <=version2.
  • range1 || range2 二选一。
  • git... 见下文’依赖Git URL’
  • user/repo 见下文’GitHub URLs’

比如下面都是合法的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{ "dependencies" :
{ "foo" : "1.0.0 - 2.9999.9999"
, "bar" : ">=1.0.2 <2.1.2"
, "baz" : ">1.0.2 <=2.3.4"
, "boo" : "2.0.1"
, "boo" : "^2.0.1"
, "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
, "asd" : "http://asdf.com/asdf.tar.gz"
, "til" : "~1.2"
, "elf" : "~1.2.3"
, "two" : "2.x"
, "thr" : "3.3.x"
}
}

依赖URL

可以指定一个tarball URL,这个tarball将在包被初始化的时候下载并初始化。

依赖Git URL

Git urls 可以是下面几种形式:

1
2
3
4
5
git://github.com/user/project.git#commit-ish
git+ssh://user@hostname:project.git#commit-ish
git+ssh://user@hostname/project.git#commit-ish
git+http://user@hostname/project/blah.git#commit-ish
git+https://user@hostname/project/blah.git#commit-ish

commit-ish是可以被git checkout的任何tag、sha或者branch。默认为master

GitHub URLs

1.1.65版后,你可以仅仅用“user/foo-project”引用GitHub urls,比如:

1
2
3
4
5
6
7
{
"name": "foo",
"version": "0.0.0",
"dependencies": {
"express": "visionmedia/express"
}
}

devDependencies

如果有人要用你的模块,但他们可能不需要你开发所使用的外部测试或者文档框架。

在这种情况下,最好将这些附属的项目列在devDependencies中。

这些东西会在根目录执行npm link或者npm install的时候初始化,并可以像其他npm配置参数一样管理

对于非特定平台的构建步骤,比如需要编译CoffeeScript,可以用prepublish脚本去实现,并把它依赖的包放在devDependency中。(译者注:prepublish定义了在执行npm publish的时候先行执行的脚本。详见scripts

比如:

1
2
3
4
5
6
7
8
9
10
11
{ "name": "ethopia-waza",
"description": "a delightfully fruity coffee varietal",
"version": "1.2.3",
"devDependencies": {
"coffee-script": "~1.6.3"
},
"scripts": {
"prepublish": "coffee -o lib/ -c src/waza.coffee"
},
"main": "lib/waza.js"
}

prepublish脚本会在publishing前运行,这样用户就不用自己去require来编译就能使用。并且在开发模式中(比如本地运行npm install)会运行这个脚本以便更好地测试。

peerDependencies

在一些场景中,如在一个host中不必须进行require时候,你想表现你的package与一个host工具或者库的兼容关键。这一般用来引用_插件_。尤其是你的模块可能要暴露一个特定的接口,并由host文档来预期和指定。

比如:

1
2
3
4
5
6
7
{
"name": "tea-latte",
"version": "1.3.5"
"peerDependencies": {
"tea": "2.x"
}
}

这能保证你的package可以只和tea的2.x版本一起初始化。npm install tea-latte可能会产生下面的依赖关系

1
2
├── tea-latte@1.3.5
└── tea@2.2.0

试图初始化另一个有会冲突的依赖的插件将导致一个错误。因此,确保你的插件的需求约束越弱越好,而不要去把它锁定到一个特定的版本。

假设这个host遵守semver规范,只改变这个package的主版本会打破你的插件。因此,如果你在package中用过每个1.x版本,就用”^1.0”或者”1.x”来表示。如果你依赖于功能介绍1.5.2,用”>= 1.5.2 < 2”。

bundledDependencies

一组包名,他们会在发布的时候被打包进去。

拼成"bundleDependencies"(缺d)也可以。

optionalDependencies

如果一个依赖可用,但你希望在它安装错误的时候npm也能继续初始化,那么你可以把它放在optionalDependencies hash中。这是一个包名到版本或者url的map,就像dependencies hash一样。只是它运行错误。

处理缺乏依赖也是你的程序的责任。比如像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
var foo = require('foo')
var fooVersion = require('foo/package.json').version
} catch (er) {
foo = null
}
if ( notGoodFooVersion(fooVersion) ) {
foo = null
}

// .. then later in your program ..

if (foo) {
foo.doFooThings()
}

optionalDependencies会覆盖dependencies中同名的项,所以通常比只放在一个地方好。

engines

你可以指定工作的node的版本:

1
{ "engines" : { "node" : ">=0.10.3 <0.12" } }

并且,像dependensies一样,如果你不指定版本或者指定“*”作为版本,那么所有版本的node都可以。

如果指定一个“engines”字段,那么npm会需要node在里面,如果“engines”被省略,npm会假定它在node上工作。

你也可以用“engines”字段来指定哪一个npm版本能更好地初始化你的程序,如:

1
{ "engines" : { "npm" : "~1.0.20" } }

记住,除非用户设置engine-strict标记,这个字段只是建议值。

engineStrict

如果你确定你的模块一定不会运行在你指定版本之外的node或者npm上,你可以在package.json文件中设置"engineStrict":true。它会重写用户的engine-strict设置。

除非你非常非常确定,否则不要这样做。如果你的engines hash过度地限制,很可能轻易让自己陷入窘境。慎重地考虑这个选择。如果大家滥用它,它会再以后的npm版本中被删除。

os

你可以指定你的模块要运行在哪些操作系统中:

1
"os" : [ "darwin", "linux" ]

你也可以用黑名单代替白名单,在名字前面加上“!”就可以了:

1
"os" : [ "!win32" ]

操作系统用process.platform来探测。

虽然没有很好地理由,但它是同时支持黑名单和白名单的。

cpu

如果你的代码只能运行在特定的cpu架构下,你可以指定一个:

1
"cpu" : [ "x64", "ia32" ]

就像os选项,你也可以黑一个架构:

1
"cpu" : [ "!arm", "!mips" ]

cpu架构用process.arch探测。

preferGlobal

如果包主要是需要全局安装的命令行程序,就设置它为true来提供一个warning给只在局部安装的人。

它不会真正的防止用户在局部安装,但如果它没有按预期工作它会帮助防止产生误会。

private

如果你设置"private": true,npm就不会发布它。

这是一个防止意外发布私有库的方式。如果你要确定给定的包是只发布在特定registry(如内部registry)的,用publishConfighash的描述来重写registry的publish-time配置参数。

publishConfig

这是一个在publish-time使用的配置集合。当你想设置tag或者registry的时候它非常有用,所以你可以确定一个给定的包没有打上“lastest”的tag或者被默认发布到全局的公开registry。

任何配置都可以被重写,但当然可能只有“tag”和“registry”与发布的意图有关。

参见npm-config(7)有可以被重写的列表。

DEFAULT VALUES

npm会根据包的内容设置一些默认值。

  • "scripts": {"start": "node server.js"}

    如果包的根目录有server.js文件,npm会默认将start命令设置为node server.js

  • "scripts":{"preinstall": "node-waf clean || true; node-waf configure build"}

    如果包的根目录有wscript文件,npm会默认将preinstall命令用node-waf进行编译。

  • "scripts":{"preinstall": "node-gyp rebuild"}

    如果包的根目录有binding.gyp文件,npm会默认将preinstall命令用node-gyp进行编译。

  • "contributors": [...]

    如果有AUTHORS文件,npm会默认逐行按Name <email> (url)格式处理,邮箱和url是可选的。#号和空格开头的行会被忽略。

配置相关—npm

简介

npm有两层含义。一层含义是Node的开放式模块登记和管理系统,网址为npmjs.org。另一层含义是Node默认的模块管理器,是一个命令行下的软件,用来安装和管理Node模块。

npm不需要单独安装。在安装Node的时候,会连带一起安装npm。但是,Node附带的npm可能不是最新版本,最好用下面的命令,更新到最新版本。

1
$ npm install npm@latest -g

上面的命令中,@latest表示最新版本,-g表示全局安装。所以,命令的主干是npm install npm,也就是使用npm安装自己。之所以可以这样,是因为npm本身与Node的其他模块没有区别。

然后,运行下面的命令,查看各种信息。

1
2
3
4
5
6
7
8
9
10
11
# 查看 npm 命令列表
$ npm help

# 查看各个命令的简单用法
$ npm -l

# 查看 npm 的版本
$ npm -v

# 查看 npm 的配置
$ npm config list -l

npm init

npm init用来初始化生成一个新的package.json文件。它会向用户提问一系列问题,如果你觉得不用修改默认配置,一路回车就可以了。

如果使用了-f(代表force)、-y(代表yes),则跳过提问阶段,直接生成一个新的package.json文件。

1
$ npm init -y

npm set

npm set用来设置环境变量。

1
2
3
4
$ npm set init-author-name 'Your name'
$ npm set init-author-email 'Your email'
$ npm set init-author-url 'http://yourdomain.com'
$ npm set init-license 'MIT'

上面命令等于为npm init设置了默认值,以后执行npm init的时候,package.json的作者姓名、邮件、主页、许可证字段就会自动写入预设的值。这些信息会存放在用户主目录的 ~/.npmrc文件,使得用户不用每个项目都输入。如果某个项目有不同的设置,可以针对该项目运行npm config

1
$ npm set save-exact true

上面命令设置加入模块时,package.json将记录模块的确切版本,而不是一个可选的版本范围。

npm config

1
$ npm config set prefix $dir

上面的命令将指定的$dir目录,设为模块的全局安装目录。如果当前有这个目录的写权限,那么运行npm install的时候,就不再需要sudo命令授权了。

1
$ npm config set save-prefix ~

上面的命令使得npm install --savenpm install --save-dev安装新模块时,允许的版本范围从克拉符号(^)改成波浪号(~),即从允许小版本升级,变成只允许补丁包的升级。

1
2
$ npm config set init.author.name $name
$ npm config set init.author.email $email

上面命令指定使用npm init时,生成的package.json文件的字段默认值。

npm info

npm info命令可以查看每个模块的具体信息。比如,查看underscore模块的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ npm info underscore
{ name: 'underscore',
description: 'JavaScript\'s functional programming helper library.',
'dist-tags': { latest: '1.5.2', stable: '1.5.2' },
repository:
{ type: 'git',
url: 'git://github.com/jashkenas/underscore.git' },
homepage: 'http://underscorejs.org',
main: 'underscore.js',
version: '1.5.2',
devDependencies: { phantomjs: '1.9.0-1' },
licenses:
{ type: 'MIT',
url: 'https://raw.github.com/jashkenas/underscore/master/LICENSE' },
files:
[ 'underscore.js',
'underscore-min.js',
'LICENSE' ],
readmeFilename: 'README.md'}

上面命令返回一个JavaScript对象,包含了underscore模块的详细信息。这个对象的每个成员,都可以直接从info命令查询。

1
2
3
4
5
6
7
8
$ npm info underscore description
JavaScript's functional programming helper library.

$ npm info underscore homepage
http://underscorejs.org

$ npm info underscore version
1.5.2

npm search命令用于搜索npm仓库,它后面可以跟字符串,也可以跟正则表达式。

1
$ npm search <搜索词>

下面是一个例子。

1
2
3
4
5
6
$ npm search node-gyp
// NAME DESCRIPTION
// autogypi Autogypi handles dependencies for node-gyp projects.
// grunt-node-gyp Run node-gyp commands from Grunt.
// gyp-io Temporary solution to let node-gyp run `rebuild` under…
// ...

npm list

npm list命令以树型结构列出当前项目安装的所有模块,以及它们依赖的模块。

1
$ npm list

加上global参数,会列出全局安装的模块。

1
$ npm list -global

npm list命令也可以列出单个模块。

1
$ npm list underscore

npm install

基本用法

Node模块采用npm install命令安装。

每个模块可以“全局安装”,也可以“本地安装”。“全局安装”指的是将一个模块安装到系统目录中,各个项目都可以调用。一般来说,全局安装只适用于工具模块,比如eslintgulp。“本地安装”指的是将一个模块下载到当前项目的node_modules子目录,然后只有在项目目录之中,才能调用这个模块。

1
2
3
4
5
6
# 本地安装
$ npm install <package name>

# 全局安装
$ sudo npm install -global <package name>
$ sudo npm install -g <package name>

npm install也支持直接输入Github代码库地址。

1
2
$ npm install git://github.com/package/path.git
$ npm install git://github.com/package/path.git#0.1.0

安装之前,npm install会先检查,node_modules目录之中是否已经存在指定模块。如果存在,就不再重新安装了,即使远程仓库已经有了一个新版本,也是如此。

如果你希望,一个模块不管是否安装过,npm 都要强制重新安装,可以使用-f--force参数。

1
$ npm install <packageName> --force

如果你希望,所有模块都要强制重新安装,那就删除node_modules目录,重新执行npm install

1
2
$ rm -rf node_modules
$ npm install

安装不同版本

install命令总是安装模块的最新版本,如果要安装模块的特定版本,可以在模块名后面加上@和版本号。

1
2
3
$ npm install sax@latest
$ npm install sax@0.1.1
$ npm install sax@">=0.1.0 <0.2.0"

如果使用--save-exact参数,会在package.json文件指定安装模块的确切版本。

1
$ npm install readable-stream --save --save-exact

install命令可以使用不同参数,指定所安装的模块属于哪一种性质的依赖关系,即出现在packages.json文件的哪一项中。

  • –save:模块名将被添加到dependencies,可以简化为参数-S
  • –save-dev: 模块名将被添加到devDependencies,可以简化为参数-D
1
2
3
4
5
$ npm install sax --save
$ npm install node-tap --save-dev
# 或者
$ npm install sax -S
$ npm install node-tap -D

如果要安装beta版本的模块,需要使用下面的命令。

1
2
3
4
5
# 安装最新的beta版
$ npm install <module-name>@beta (latest beta)

# 安装指定的beta版
$ npm install <module-name>@1.3.1-beta.3

npm install默认会安装dependencies字段和devDependencies字段中的所有模块,如果使用--production参数,可以只安装dependencies字段的模块。

1
2
3
$ npm install --production
# 或者
$ NODE_ENV=production npm install

一旦安装了某个模块,就可以在代码中用require命令加载这个模块。

1
2
var backbone = require('backbone')
console.log(backbone.VERSION)

避免系统权限

默认情况下,Npm全局模块都安装在系统目录(比如/usr/local/lib/),普通用户没有写入权限,需要用到sudo命令。这不是很方便,我们可以在没有root权限的情况下,安装全局模块。

首先,在主目录下新建配置文件.npmrc,然后在该文件中将prefix变量定义到主目录下面。

1
prefix = /home/yourUsername/npm

然后在主目录下新建npm子目录。

1
$ mkdir ~/npm

此后,全局安装的模块都会安装在这个子目录中,npm也会到~/npm/bin目录去寻找命令。

最后,将这个路径在.bash_profile文件(或.bashrc文件)中加入PATH变量。

1
export PATH=~/npm/bin:$PATH

npm update,npm uninstall

npm update命令可以更新本地安装的模块。

1
2
3
4
5
# 升级当前项目的指定模块
$ npm update [package name]

# 升级全局安装的模块
$ npm update -global [package name]

它会先到远程仓库查询最新版本,然后查询本地版本。如果本地版本不存在,或者远程版本较新,就会安装。

使用-S--save参数,可以在安装的时候更新package.json里面模块的版本号。

1
2
3
4
5
6
7
8
9
// 更新之前的package.json
dependencies: {
dep1: "^1.1.1"
}

// 更新之后的package.json
dependencies: {
dep1: "^1.2.2"
}

注意,从npm v2.6.1 开始,npm update只更新顶层模块,而不更新依赖的依赖,以前版本是递归更新的。如果想取到老版本的效果,要使用下面的命令。

1
$ npm --depth 9999 update

npm uninstall命令,卸载已安装的模块。

1
2
3
4
$ npm uninstall [package name]

# 卸载全局模块
$ npm uninstall [package name] -global

npm run

npm不仅可以用于模块管理,还可以用于执行脚本。package.json文件有一个scripts字段,可以用于指定脚本命令,供npm直接调用。

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "myproject",
"devDependencies": {
"jshint": "latest",
"browserify": "latest",
"mocha": "latest"
},
"scripts": {
"lint": "jshint **.js",
"test": "mocha test/"
}
}

上面代码中,scripts字段指定了两项命令linttest。命令行输入npm run-script lint或者npm run lint,就会执行jshint **.js,输入npm run-script test或者npm run test,就会执行mocha test/npm runnpm run-script的缩写,一般都使用前者,但是后者可以更好地反应这个命令的本质。

npm run命令会自动在环境变量$PATH添加node_modules/.bin目录,所以scripts字段里面调用命令时不用加上路径,这就避免了全局安装NPM模块。

npm run如果不加任何参数,直接运行,会列出package.json里面所有可以执行的脚本命令。

npm内置了两个命令简写,npm test等同于执行npm run testnpm start等同于执行npm run start

npm run会创建一个Shell,执行指定的命令,并临时将node_modules/.bin加入PATH变量,这意味着本地模块可以直接运行。

举例来说,你执行ESLint的安装命令。

1
$ npm i eslint --save-dev

运行上面的命令以后,会产生两个结果。首先,ESLint被安装到当前目录的node_modules子目录;其次,node_modules/.bin目录会生成一个符号链接node_modules/.bin/eslint,指向ESLint模块的可执行脚本。

然后,你就可以在package.jsonscript属性里面,不带路径的引用eslint这个脚本。

1
2
3
4
5
6
7
8
9
{
"name": "Test Project",
"devDependencies": {
"eslint": "^1.10.3"
},
"scripts": {
"lint": "eslint ."
}
}

等到运行npm run lint的时候,它会自动执行./node_modules/.bin/eslint .

如果直接运行npm run不给出任何参数,就会列出scripts属性下所有命令。

1
2
3
4
5
6
$ npm run
Available scripts in the user-service package:
lint
jshint **.js
test
mocha test/

下面是另一个package.json文件的例子。

1
2
3
4
5
6
"scripts": {
"watch": "watchify client/main.js -o public/app.js -v",
"build": "browserify client/main.js -o public/app.js",
"start": "npm run watch & nodemon server.js",
"test": "node test/all.js"
},

上面代码在scripts项,定义了四个别名,每个别名都有对应的脚本命令。

1
2
3
4
$ npm run watch
$ npm run build
$ npm run start
$ npm run test

其中,starttest属于特殊命令,可以省略run

1
2
$ npm start
$ npm test

如果希望一个操作的输出,是另一个操作的输入,可以借用Linux系统的管道命令,将两个操作连在一起。

1
"build-js": "browserify browser/main.js | uglifyjs -mc > static/bundle.js"

但是,更方便的写法是引用其他npm run命令。

1
"build": "npm run build-js && npm run build-css"

上面的写法是先运行npm run build-js,然后再运行npm run build-css,两个命令中间用&&连接。如果希望两个命令同时平行执行,它们中间可以用&连接。

下面是一个流操作的例子。

1
2
3
4
5
6
7
8
"devDependencies": {
"autoprefixer": "latest",
"cssmin": "latest"
},

"scripts": {
"build:css": "autoprefixer -b 'last 2 versions' < assets/styles/main.css | cssmin > dist/main.css"
}

写在scripts属性中的命令,也可以在node_modules/.bin目录中直接写成bash脚本。下面是一个bash脚本。

1
2
3
4
#!/bin/bash

cd site/main
browserify browser/main.js | uglifyjs -mc > static/bundle.js

假定上面的脚本文件名为build.sh,并且权限为可执行,就可以在scripts属性中引用该文件。

1
"build-js": "bin/build.sh"

参数

npm run命令还可以添加参数。

1
2
3
"scripts": {
"test": "mocha test/"
}

上面代码指定npm test,实际运行mocha test/。如果要通过npm test命令,将参数传到mocha,则参数之前要加上两个连词线。

1
2
3
$ npm run test -- anothertest.js
# 等同于
$ mocha test/ anothertest.js

上面命令表示,mocha要运行所有test子目录的测试脚本,以及另外一个测试脚本anothertest.js

npm run本身有一个参数-s,表示关闭npm本身的输出,只输出脚本产生的结果。

1
2
3
4
5
// 输出npm命令头
$ npm run test

// 不输出npm命令头
$ npm run -s test

scripts脚本命令最佳实践

scripts字段的脚本命令,有一些最佳实践,可以方便开发。首先,安装npm-run-all模块。

1
$ npm install npm-run-all --save-dev

这个模块用于运行多个scripts脚本命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 继发执行
$ npm-run-all build:html build:js
# 等同于
$ npm run build:html && npm run build:js

# 并行执行
$ npm-run-all --parallel watch:html watch:js
# 等同于
$ npm run watch:html & npm run watch:js

# 混合执行
$ npm-run-all clean lint --parallel watch:html watch:js
# 等同于
$ npm-run-all clean lint
$ npm-run-all --parallel watch:html watch:js

# 通配符
$ npm-run-all --parallel watch:*

(1)start脚本命令

start脚本命令,用于启动应用程序。

1
"start": "npm-run-all --parallel dev serve"

上面命令并行执行dev脚本命令和serve脚本命令,等同于下面的形式。

1
$ npm run dev & npm run serve

如果start脚本没有配置,npm start命令默认执行下面的脚本,前提是模块的根目录存在一个server.js文件。

1
$ node server.js

(2)dev脚本命令

dev脚本命令,规定开发阶段所要做的处理,比如构建网页资源。

1
"dev": "npm-run-all dev:*"

上面命令用于继发执行所有dev的子命令。

1
"predev:sass": "node-sass --source-map src/css/hoodie.css.map --output-style nested src/sass/base.scss src/css/hoodie.css"

上面命令将sass文件编译为css文件,并生成source map文件。

1
"dev:sass": "node-sass --source-map src/css/hoodie.css.map --watch --output-style nested src/sass/base.scss src/css/hoodie.css"

上面命令会监视sass文件的变动,只要有变动,就自动将其编译为css文件。

1
"dev:autoprefix": "postcss --use autoprefixer --autoprefixer.browsers \"> 5%\" --output src/css/hoodie.css src/css/hoodie.css"

上面命令为css文件加上浏览器前缀,限制条件是只考虑市场份额大于5%的浏览器。

(3)serve脚本命令

serve脚本命令用于启动服务。

1
"serve": "live-server dist/ --port=9090"

上面命令启动服务,用的是live-server模块,将服务启动在9090端口,展示dist子目录。

live-server模块有三个功能。

  • 启动一个HTTP服务器,展示指定目录的index.html文件,通过该文件加载各种网络资源,这是file://协议做不到的。
  • 添加自动刷新功能。只要指定目录之中,文件有任何变化,它就会刷新页面。
  • npm run serve命令执行以后,自动打开浏览器。、

以前,上面三个功能需要三个模块来完成:http-serverlive-reloadopener,现在只要live-server一个模块就够了。

(4)test脚本命令

test脚本命令用于执行测试。

1
2
"test": "npm-run-all test:*",
"test:lint": "sass-lint --verbose --config .sass-lint.yml src/sass/*"

上面命令规定,执行测试时,运行lint脚本,检查脚本之中的语法错误。

(5)prod脚本命令

prod脚本命令,规定进入生产环境时需要做的处理。

1
2
3
"prod": "npm-run-all prod:*",
"prod:sass": "node-sass --output-style compressed src/sass/base.scss src/css/prod/hoodie.min.css",
"prod:autoprefix": "postcss --use autoprefixer --autoprefixer.browsers "> 5%" --output src/css/prod/hoodie.min.css src/css/prod/hoodie.min.css"

上面命令将sass文件转为css文件,并加上浏览器前缀。

(6)help脚本命令

help脚本命令用于展示帮助信息。

1
"help": "markdown-chalk --input DEVELOPMENT.md"

上面命令之中,markdown-chalk模块用于将指定的markdown文件,转为彩色文本显示在终端之中。

(7)docs脚本命令

docs脚本命令用于生成文档。

1
"docs": "kss-node --source src/sass --homepage ../../styleguide.md"

上面命令使用kss-node模块,提供源码的注释生成markdown格式的文档。

pre- 和 post- 脚本

npm run为每条命令提供了pre-post-两个钩子(hook)。以npm run lint为例,执行这条命令之前,npm会先查看有没有定义prelint和postlint两个钩子,如果有的话,就会先执行npm run prelint,然后执行npm run lint,最后执行npm run postlint

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "myproject",
"devDependencies": {
"eslint": "latest"
"karma": "latest"
},
"scripts": {
"lint": "eslint --cache --ext .js --ext .jsx src",
"test": "karma start --log-leve=error karma.config.js --single-run=true",
"pretest": "npm run lint",
"posttest": "echo 'Finished running tests'"
}
}

上面代码是一个package.json文件的例子。如果执行npm test,会按下面的顺序执行相应的命令。

  1. pretest
  2. test
  3. posttest

如果执行过程出错,就不会执行排在后面的脚本,即如果prelint脚本执行出错,就不会接着执行lint和postlint脚本。

下面是一个例子。

1
2
3
4
5
{
"test": "karma start",
"test:lint": "eslint . --ext .js --ext .jsx",
"pretest": "npm run test:lint"
}

上面代码中,在运行npm run test之前,会自动检查代码,即运行npm run test:lint命令。

下面是一些常见的pre-post-脚本。

  • prepublish:发布一个模块前执行。
  • postpublish:发布一个模块后执行。
  • preinstall:用户执行npm install命令时,先执行该脚本。
  • postinstall:用户执行npm install命令时,安装结束后执行该脚本,通常用于将下载的源码编译成用户需要的格式,比如有些模块需要在用户机器上跟本地的C++模块一起编译。
  • preuninstall:卸载一个模块前执行。
  • postuninstall:卸载一个模块后执行。
  • preversion:更改模块版本前执行。
  • postversion:更改模块版本后执行。
  • pretest:运行npm test命令前执行。
  • posttest:运行npm test命令后执行。
  • prestop:运行npm stop命令前执行。
  • poststop:运行npm stop命令后执行。
  • prestart:运行npm start命令前执行。
  • poststart:运行npm start命令后执行。
  • prerestart:运行npm restart命令前执行。
  • postrestart:运行npm restart命令后执行。

对于最后一个npm restart命令,如果没有设置restart脚本,prerestartpostrestart会依次执行stop和start脚本。

另外,不能在pre脚本之前再加pre,即prepretest脚本不起作用。

注意,即使Npm可以自动运行prepost脚本,也可以手动执行它们。

1
$ npm run prepublish

下面是post install的例子。

1
2
3
{
"postinstall": "node lib/post_install.js"
}

上面的这个命令,主要用于处理从Git仓库拉下来的源码。比如,有些源码是用TypeScript写的,可能需要转换一下。

下面是publish钩子的一个例子。

1
2
3
4
5
6
7
{
"dist:modules": "babel ./src --out-dir ./dist-modules",
"gh-pages": "webpack",
"gh-pages:deploy": "gh-pages -d gh-pages",
"prepublish": "npm run dist:modules",
"postpublish": "npm run gh-pages && npm run gh-pages:deploy"
}

上面命令在运行npm run publish时,会先执行Babel编译,然后调用Webpack构建,最后发到Github Pages上面。

以上都是npm相关操作的钩子,如果安装某些模块,还能支持Git相关的钩子。下面以husky模块为例。

1
$ npm install husky --save-dev

安装以后,就能在package.json添加precommitprepush等钩子。

1
2
3
4
5
6
7
8
{
"scripts": {
"lint": "eslint yourJsFiles.js",
"precommit": "npm run test && npm run lint",
"prepush": "npm run test && npm run lint",
"...": "..."
}
}

类似作用的模块还有pre-commitprecommit-hook等。

内部变量(script字段和config字段)

scripts字段可以使用一些内部变量,主要是package.json的各种字段。

比如,package.json的内容是{"name":"foo", "version":"1.2.5"},那么变量npm_package_name的值是foo,变量npm_package_version的值是1.2.5。

1
2
3
4
5
{
"scripts":{
"bundle": "mkdir -p build/$npm_package_version/"
}
}

运行npm run bundle以后,将会生成build/1.2.5/子目录。

config字段也可以用于设置内部字段。

1
2
3
4
5
6
7
"name": "fooproject",
"config": {
"reporter": "xunit"
},
"scripts": {
"test": "mocha test/ --reporter $npm_package_config_reporter"
}

上面代码中,变量npm_package_config_reporter对应的就是reporter。

通配符

npm的通配符的规则如下。

  • * 匹配0个或多个字符
  • ? 匹配1个字符
  • [...] 匹配某个范围的字符。如果该范围的第一个字符是!^,则匹配不在该范围的字符。
  • !(pattern|pattern|pattern) 匹配任何不符合给定的模式
  • ?(pattern|pattern|pattern) 匹配0个或1个给定的模式
  • +(pattern|pattern|pattern) 匹配1个或多个给定的模式
  • *(a|b|c) 匹配0个或多个给定的模式
  • @(pattern|pat*|pat?erN) 只匹配给定模式之一
  • ** 如果出现在路径部分,表示0个或多个子目录。

开发NPM模块的时候,有时我们会希望,边开发边试用,比如本地调试的时候,require('myModule')会自动加载本机开发中的模块。Node规定,使用一个模块时,需要将其安装到全局的或项目的node_modules目录之中。对于开发中的模块,解决方法就是在全局的node_modules目录之中,生成一个符号链接,指向模块的本地目录。

npm link就能起到这个作用,会自动建立这个符号链接。

请设想这样一个场景,你开发了一个模块myModule,目录为src/myModule,你自己的项目myProject要用到这个模块,项目目录为src/myProject。首先,在模块目录(src/myModule)下运行npm link命令。

1
src/myModule$ npm link

上面的命令会在NPM的全局模块目录内,生成一个符号链接文件,该文件的名字就是package.json文件中指定的模块名。

1
/path/to/global/node_modules/myModule -> src/myModule

这个时候,已经可以全局调用myModule模块了。但是,如果我们要让这个模块安装在项目内,还要进行下面的步骤。

切换到项目目录,再次运行npm link命令,并指定模块名。

1
src/myProject$ npm link myModule

上面命令等同于生成了本地模块的符号链接。

1
src/myProject/node_modules/myModule -> /path/to/global/node_modules/myModule

然后,就可以在你的项目中,加载该模块了。

1
var myModule = require('myModule');

这样一来,myModule的任何变化,都可以直接反映在myProject项目之中。但是,这样也出现了风险,任何在myProject目录中对myModule的修改,都会反映到模块的源码中。

如果你的项目不再需要该模块,可以在项目目录内使用npm unlink命令,删除符号链接。

1
src/myProject$ npm unlink myModule

npm bin

npm bin命令显示相对于当前目录的Node模块的可执行脚本所在的目录(即.bin目录)。

1
2
3
# 项目根目录下执行
$ npm bin
./node_modules/.bin

npm adduser

npm adduser用于在npmjs.com注册一个用户。

1
2
3
4
$ npm adduser
Username: YOUR_USER_NAME
Password: YOUR_PASSWORD
Email: YOUR_EMAIL@domain.com

npm publish

npm publish用于将当前模块发布到npmjs.com。执行之前,需要向npmjs.com申请用户名。

1
$ npm adduser

如果已经注册过,就使用下面的命令登录。

1
$ npm login

登录以后,就可以使用npm publish命令发布。

1
$ npm publish

如果当前模块是一个beta版,比如1.3.1-beta.3,那么发布的时候需要使用tag参数,将其发布到指定标签,默认的发布标签是latest

1
$ npm publish --tag beta

如果发布私有模块,模块初始化的时候,需要加上scope参数。只有npm的付费用户才能发布私有模块。

1
$ npm init --scope=<yourscope>

如果你的模块是用ES6写的,那么发布的时候,最好转成ES5。首先,需要安装Babel。

1
$ npm install --save-dev babel-cli@6 babel-preset-es2015@6

然后,在package.json里面写入build脚本。

1
2
3
4
"scripts": {
"build": "babel source --presets babel-preset-es2015 --out-dir distribution",
"prepublish": "npm run build"
}

运行上面的脚本,会将source目录里面的ES6源码文件,转为distribution目录里面的ES5源码文件。然后,在项目根目录下面创建两个文件.npmignore.gitignore,分别写入以下内容。

1
2
3
4
5
6
// .npmignore
source

// .gitignore
node_modules
distribution

npm deprecate

如果想废弃某个版本的模块,可以使用npm deprecate命令。

1
$ npm deprecate my-thing@"< 0.2.3" "critical bug fixed in v0.2.3"

运行上面的命令以后,小于0.2.3版本的模块的package.json都会写入一行警告,用户安装这些版本时,这行警告就会在命令行显示。

npm owner

模块的维护者可以发布新版本。npm owner命令用于管理模块的维护者。

1
2
3
4
5
6
7
8
# 列出指定模块的维护者
$ npm owner ls <package name>

# 新增维护者
$ npm owner add <user> <package name>

# 删除维护者
$ npm owner rm <user> <package name>

其他命令

npm home,npm repo

npm home命令可以打开一个模块的主页,npm repo命令则是打开一个模块的代码仓库。

1
2
$ npm home $package
$ npm repo $package

这两个命令不需要模块先安装。

npm outdated

npm outdated命令检查当前项目所依赖的模块,是否已经有新版本。

1
$ npm outdated

它会输出当前版本(current version)、应当安装的版本(wanted version)和最新发布的版本(latest version)。

npm prune

npm prune检查当前项目的node_modules目录中,是否有package.json里面没有提到的模块,然后将所有这些模块输出在命令行。

1
$ npm prune

npm shrinkwrap

npm shrinkwrap的作用是锁定当前项目的以来模块的版本。

1
$ npm shrinkwrap

运行该命令后,会在当前项目的根目录下生成一个npm-shrinkwrap.json文件,内容是node_modules目录下所有已经安装模块的版本。

下次运行npm install命令时,npm发现当前目录下有npm-shrinkwrap.json文件,就会只安装里面提到的模块,且版本也会保持一致。

react-redux,applyMiddleware

react-redux详细教程请参考阮一峰的文章:react-redux,这里只是介绍一些基本用法,及自己遇到的一点痛点,applyMiddleware先介绍 两个常用的redux-thunkredux-logger

统一整理(想到哪写到哪…)

项目源码在github上戳react-plus

一,react-redux有什么用?

​ React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。

  • UI组件只负责 UI 的呈现,不带有任何业务逻辑

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      render() {
      /**
      *state,add,del是哪里的?就是函数mapStateToProps和mapDispatchToProps返回的
      **/
      const {state,add,del}=this.props;
      return (
      <View style={styles.container}>
      <View style={styles.item}>
      <Text style={{color: 'red'}}>{state.sum}</Text>
      </View>
      <View style={[styles.item, {top: 10}]}>
      <TouchableWithoutFeedback onPress={add} >
      <View style={styles.touch}>
      <Text style={styles.but}>+</Text>
      </View>
      </TouchableWithoutFeedback>
      <View>
      <View>)
      }
  • 所有数据都由参数(this.props)提供

    • React-Redux 提供Provider组件,可以让容器组件拿到state

    • 1
      2
      3
      4
      5
      6
      7
      render(
      <Provider store={store}>
      <App />
      //然后App组件就可以通过this.props获取`每个组件JS文件内部定义的mapStateToProps中返回的state和mapDispatchToProps中返回的回调方法
      </Provider>,
      document.getElementById('root')
      )
  • React-Redux 提供connect方法,用于从 UI 组件生成容器组件

    • 1
      2
      3
      4
      5
      6
      function mapStateToProps(state){
      return {
      state: state.Index1
      //state是全局的state,Index1是Index1(reducer)返回的state,因为state内部包含所有reducer返回的state,所以这里需要用.语法确定需要传递那个reducer返回的状态给UI组件使用
      }
      }
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      function mapDispatchToProps(dispatch) {
      return{
      add: () => dispatch(IndexAction.add())①
      //IndexAction.add()执行Action中的一个函数,返回一个action对象
      }
      }

      /*action add()函数下面这样
      export function add() {
      return {
      type: 'INDEX_ADD'
      }
      }
      */
    • 1
      2
      export default connect(  mapStateToProps,  mapDispatchToProps)(UIComponent)
      //导出容器组件
  • 容器组件负责管理数据和业务逻辑

  • Reducer触发了mapDispatchToProps中的方法后自动执行

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      import * as types from '../constant/ActionTypes';

      const initialState = {
      sum:0
      };
      export default function IndexReducer(state = initialState, action = {}) {
      switch (action.type){
      case types.INDEX_ADD: ②
      return {
      ...state,
      sum:state.sum+1
      };
      break;
      case types.INDEX_DEL:
      return {
      ...state,
      sum:state.sum-1
      };
      break;
      default:
      return state;
      }
      }

      //执行了上面①出的dispatch方法后②出的reducer就会执行并返回最新的状态,那么问题来了,如果我想再来一个➕1的UI,怎么实现呢?直接增加一个UI容器组件,然后加一个reducer像下面这样?

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      import * as types from '../constant/ActionTypes';
      const initialState = {
      sum:0
      };
      export default function IndexReducer2(state = initialState, action = {}) {
      switch (action.type){
      case types.INDEX_ADD:
      return {
      ...state,
      sum:state.sum+1
      };
      break;
      case types.INDEX_DEL:
      return {
      ...state,
      sum:state.sum-1
      };
      break;
      case types.INDEX_INIT:
      return {
      ...state,
      sum:action.param!=undefined
      };
      break;
      default:
      return state;
      }
      }

      结果就是两个加操作会一起执行,点击第一个加按钮,第二个一起增加,原因是因为点击第一个加按钮和点击第二个加按钮执行的mapDispatchToProps方法都是触发相同的action:{ type: 'INDEX_ADD'}所以会触发所有监听此action的所有的reducer,解决的办法就是每个reducer监听自己的action,只要在action增加一个function add2(){ return { type: types.INDEX2_ADD} },然后设置IndexReducer2,

      case types.INDEX2_ADD:
      ​ return {
      ​ …state,
      ​ sum:state.sum+1
      ​ };
      ​ break;

      即可完各自reducer监听各自的action执行并返回各自的state

二,redux-logger有什么用?

就是说为了方便调试,redux-logger中间键会加入到store中后会自动监听状态更新,并在控制台打印出向相关信息

1
2
3
4
5
6
7
8
9
10
action @ 00:29:13.950 INDEX_ADD
prev state Object
IndexReduer:Object
sum:0
action Object
type:'INDEX_ADD'
next state Object
IndexReduer:Object
sum:1
这样的相关信息...

三,加入中间键后store的创建方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// rootReducer.js
import { combineReducers } from 'redux';
import IndexReducer from './IndexReducer';
import IndexReducer2 from './indexReducer2';
const RootReducer = combineReducers({
IndexReducer,IndexReducer2 //通过combineReducers合成一个总rootReducer
});
export default RootReducer;



// configure-store.js //用于生成store
import {createStore, applyMiddleware} from 'redux';
import rootReducer from '../reducer/RootReducer';
import thunk from 'redux-thunk';
const middlewares = [thunk];

const createLogger = require('redux-logger');
if (process.env.NODE_ENV === 'development') {
const logger = createLogger();
//redux 日志打印
middlewares.push(logger);
}
// store生成方式一,网上好像这样方式还比较多
const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
export default function configureStore(initialState) {
const store = createStoreWithMiddleware(rootReducer, initialState);
return store;
}
// store生成方式二,还是感觉这种比较简单(可以直接在主界面使用)
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState, applyMiddleware(...middlewares));
return store;
}

三,redux-thunk有什么用?

redux-thunk,是为了异步使用dispatch方法而使用的,和redux-logger一样,由store加入后自动生效,使用场景?我来小举一例:

例如上面的点击加一操作是这样的

1
2
3
4
5
6
7
1,  const {state,add,del}=this.props;
2, <Text style={styles.but} onPress={add}>+</Text>
3, function mapDispatchToProps(dispatch) {
return{
add: () => dispatch({type:'INDEX_ADD'})
}
}

那么我现在我需要实现点击第一个加一按钮3秒才执行加一,

不用redux-thunk我们可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
1, const {state,add,del}=this.props;
2, startAdd(){
setTimeout(function(){
add();
},3000)
}
3, <Text style={styles.but} onPress={this.startAdd}>+</Text>
4, function mapDispatchToProps(dispatch) {
return{
add: () => dispatch({type:'INDEX_ADD'})
}
}

使用了redux-thunk后可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
1, const {state,add,del}=this.props;
2, <Text style={styles.but} onPress={add}>+</Text>
3, function mapDispatchToProps(dispatch) {
return{
add: () => dispatch(function(dispatch,getState){
console.log(getState())
setTimeout(function(){
dispatch({type:'INDEX_ADD'})
},3000)
})
}
}

也就是说本来只能传入{}作为参数的dispatch用了redux-thunk后可以传一个函数作为参数,然后再这个函数内部执行dispatch就可以了,所有可以在函数内部执行setTimeout和fetch等方法了,实现异步的redux操作,同时thunk还支持第二个参数,用来获取全局state!

redux入门

个人只是把redux和vuex当做进行统一状态管理的工具,那个和上一篇vuex一样,这里还是举个相同的加减栗子来对redux做个入门介绍

Redux 基础概念

State

State 描述一个应用程序的全部状态,所有需要控制的状态都应设计到 state 对象中

Action

Action 用来描述一个行为,并负载相应的信息。

Reducer

Reducer 接收两个参数,第一个参数是当前状态,而第二参数是 action, 返回更新后的状态。
用来执行store触发的Action对应的方法

Store

Store 是管理应用程序 state 的容器

store = {
    getState,
    dispatch,
    subscribe,
    replaceReducer
}

如上,store共有4个API,

getState: (数据接口)
使用方式: store.getState()
功能点: redux对外导出数据的接口(getter)

subscribe: (订阅接口)
使用方式: store.subscribe(回调函数)
功能点: 在每次执行dispatch的时候,用于执行自定义的回调函数操作

dispatch: (执行接口)
使用方式: store.dispatch(action)
功能点: 在执行redux的state更新的同时,执行所有subscribe过的回调函数

replaceReducer: (重置store接口)
使用方式: store.replaceReducer(nextReducer)
功能点: 重置redux的reducer,重新启动store流程

流程图如下

项目实战:

首先安装redux到项目中

npm install --save redux

代码编写

HTML

<p>
    Clicked: <span id="value">0</span>
    <button id="increment">+</button>
    <button id="decrement">-</button>
</p>

JS

//导入createStroe
import { createStore } from 'redux'

//初始化状态
let initState = {
      count: 0,
      score: 0,
      description:'描述'
}

//创建一个Reducer
function counter(state = initState, action) {
    switch (action.type) {
        case 'INCREMENT':
              return{
                  count:state.count + 1,
                score:state.score + 2,
                description:action.data
              } 
        case 'DECREMENT':
              return{
                  count:state.count - 1,
                score:state.score - 2,
                description:action.data
              } 
        default:
              return state;
          }
    }

//将创建好的Reducer传入创建Store
let store = Redux.createStore(counter);

//获取元素节点,并绑定点击事件,点击后store将触发相应的动作Action
let valueEl = document.getElementById('value');
document.getElementById('increment').addEventListener('click', function() {
    store.dispatch({
          type: 'INCREMENT',
          data:'增加的'
    })
})
document.getElementById('decrement').addEventListener('click', function() {
    store.dispatch({
          type: 'DECREMENT',
          data:'减少的',
    })
})

//写一个render方法,在这里动态改变元素的内容
function render() {
    valueEl.innerHTML = '点击'+store.getState().count.toString()+'次-得        分'+store.getState().score.toString()+store.getState().description;
}

//执行render方法
render();

//绑定subscribe回调
store.subscribe(render);
上述JS文件,当按钮点击的时候 redux的状态管理者store调用dispatch API触发相应type的Action,然后Reducer就会通过对应的Acton执行对应的方法,改变Redux的状态,此时虽然状态改变了,但是valueEl.innerHTML的值并没有改变,此时需要调用store.subscribe(render)函数执行回调函数render()

OVER

import

作为一个初学者,在如何导入/导出自己写的组件时,有时也是摸不着头脑,想我当年一样喽☺

下面列举几个常见的栗子🌰

1. import React ,{ Component } from ‘react’;

这是RN 0.26后导入React的方式,这意思是,导入‘react’文件里export的一个默认的组件,将其命名为React以及Component这个非默认组件

2.import Home from ‘./Home’;

这是导入‘Home’文件里export的带default关键字的组件,即默认组件,将其命名为Home(可以自定义命名)

3.import { Home } from ‘./Home’;

导入‘Home’文件里export的叫Home的 非默认组件,注意,非默认,非默认,非默认,以及命名Home,命名Home,命名Home

4.import { Home , Discover } from ‘/Home’;

跟3的差不多,不过是{  },可以导入多个组件,用,隔开就可以

5.import * as Home from’./compoments/Home’;

意思是将./compoments/Home'文件里的所有非默认组件,全部集结成一个Home模型组件,命名可以自定义,然后可以通过点语法,来使用组件里面的所有export的组件,例如:

fetch faild in reactnative

fetch在ReactNative中可能会报错如下:Possible unhandled promise rejection (id:0: Network request failed

According to this post, you should enable it in XCode.

  1. Click on your project in the Project Navigator
  2. Open the Info tab
  3. Click on the down arrow left to the “App Transport Security Settings”
  4. Right click on “App Transport Security Settings” and select Add Row
  5. For created row set the key “Allow Arbitrary Loads“, type to boolean and value to YES.