Shero.

生活百般滋味,你要笑着面对😊


  • 首页

  • 关于

  • 标签

  • 分类

  • 归档

dva+umi

发表于 2021-01-14 | 分类于 前端框架

dva+umi理解

dva

  • why dva?
  • redux数据流的控制可以让应用更控,让逻辑更清晰
  • redux同时会有的疑问?概念太多,并且reducer、saga、action都是分离的(文件分离
    • 编辑成本高,需要在reducer、saga、action之间来回切换
    • 不便于组织义务模型(domain models),如:每个模块功能,需要复制很多文件
    • saga书写复杂,每监听一个action都要走fork -> watcher -> worker流程
    • entry书写麻烦
  • what’s dva?

    • dva是基于现有应用框架(redux + redux-saga + react-router等)的轻量封装,没有引入任何新的概念
    • dva是framework,不是library,类似emberjs,会明确的告诉你每个部件应该怎么写,对于团队而言,会更可控。除了react和rect-dom是peerDependencies以外,dva封装了所有其它依赖
    • dva实现上尽量不创建新语法,而是依赖库本身的语法
    • dva的核心是提供了app.model方法,用于把reducer、initialState、saga、action封装到一起

      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
      app.model({
      namespace: 'products',
      state: {
      list: [],
      loading: false,
      },
      subscriptions: [
      function(dispatch) {
      dispatch({type: 'products/query'});
      },
      ],
      effects: {
      ['products/query']: function*() {
      yield call(delay(800));
      yield put({
      type: 'products/query/success',
      payload: ['ant-tool', 'roof'],
      });
      },
      },
      reducers: {
      ['products/query'](state) {
      return { ...state, loading: true, };
      },
      ['products/query/success'](state, { payload }) {
      return { ...state, loading: false, list: payload };
      },
      },
      });
    • 维护简单、提高开发效率

    • 对原有知识点组合,对API对使用

umi

  • dva+umi关系
    Image text
  • dva+umi数据共享
    Image text

typescript

发表于 2021-01-14 | 分类于 JS

typeScript

发展

  • 任何能有js实现的应用最终都会使用js实现
  • 从移动终端到后端服务
  • 从IOT到神经网络,它几乎无处不在,如此广阔的应用领域,自然对语言的安全性、健壮行、可维护性有更高的要求,尽管ES标准在近几年有长足的进步,但在类型检查方面依然无所建树
  • 你是否经常遇到这样但场景
    • 调用一个别人写但函数,很不幸,没有留下任何注释,为了搞清楚参数类型,硬着头皮看逻辑
    • 为了保证代码的健壮性,对一个函数的输入参数进行各种假设
    • 领导看好你,维护一个重要的底层类库,你殚精竭虑优化了一个参数类型,但不知道有多少处引用,是否在提交代码前,感到脊背发凉
    • 明明定义好接口,可一联调就报错了,不能阅读到length
  • 以上归根结底,js是一门动态弱类型语言,对变量的类型非常宽容,不会在这些变量和他们的调用者之间建立结构化的契约,如果长期在没有类型约束的环境下开发,造成类型思维的缺失

定义

  • 官方定义:拥有类型系统的JavaScript的超集,可以编译成纯的JavaScript
  • 类型检查:编译代码时,进行严格的静态类型检查,即编码阶段发现一些可避免的隐患,不必带到生产
  • 语言扩展:包括ES6、未来提案中的特性,如异步操作、装饰器,也会从其它语言借鉴某些特性,如接口、抽象类
  • 工具属性:ts可以编译成标准的js,可以在任何浏览器、操作系统上运行,无需任何运行时的额外开销,从这个角度讲,ts更像一个工具而非语言

    使用ts好处

  • vs code具有强大的自动补全、导航、重构功能,使得接口定义可以直接代替文档,同时可以提高开发效率,降低维护成本,更重要的是ts可以帮助团队,重塑类型思维,接口的提供方将被迫去思考API的边界,将从代码的编写者蜕变为代码的设计者
  • 如果js是野马,ts就是束缚这匹野马的缰绳,作为骑士的你,自然可以张开双臂、放飞自我,但如果不是技艺超群,恐怕会摔得很惨,然而如果抓住缰绳,你即可闲庭信步,亦可策马扬鞭,这就是ts的价值,它可让你在前端开发路上走得更稳,更远

安装 TypeScript 并运行 ts 文件

1
2
3
4
5
6
7
8
9
10
* npm install typescript -g
* yarn global add typescript
* 使用 tsc 命令转成 js 文件
* tsc xx.ts
* node xx.js
* 直接运行ts文件可以使用ts-node
* 安装ts-node
* npm install ts-node -g
* 使用
* ts-node xx.ts

TS基础数据类型

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
// string
let s: string = 'aaa';
console.log(s)

// number
let num: number = 123;
console.log(num)

// 不声明具体数据类型,由代码推断出数据类型
let a = '122';
console.log(a)

// 布尔
let b: boolean = false;
console.log(b)

// 数组 数据类型[]
let arr: string[] = ['11', '22'];
console.log(arr)

// void
function funA(): void {
console.log('定义了一个没有返回值的函数')
}
funA();

// function funNum(): number {
// console.log('定义了一个有返回值的函数')
// return 1;
// }

// null
// undefined

TS接口

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
// 接口的声明
interface inf {

}

// 接口必须的成员变量
interface inf {
name: string
}

// 接口可选的成员变量,可以有,可以没有
interface inf {
age?: number
}

// 复杂的接口定义
interface inf {
name: string
age?: number
say: () => void
userList: string[]
}

// 接口的继承
interface child extends inf {

}

TS泛型

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
class fx {
name: string
age: number
say() {
console.log(`${this.name}---${this.age}岁`)
}
}

let f = new fx();
f.name = 'heihei';
f.age = 18;
f.say();

class fx1<IProps = {}> {
user: IProps
say() {
console.log(this.user);
}
}

interface IUser {
name: string
age: number
}

// 泛型只关注为需要的数据,不关注具体的数据格式
let f1 = new fx1<IUser>();
f1.user = {age: 18, name: 'aa'};
f1.say();

TS枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
enum Direction {
UP = 'up',
DOWN = 'down',
LEFT = 'left',
RIght = 'right'
}

enum User {
UP = 'up',
}

console.log(Direction.UP)
// 简单理解 有自己的独立作用域
// console.log(Direction.UP === User.UP)

const a = 1;
const b = 1;
console.log(a == b)

vue自定义指令

发表于 2021-01-05 | 分类于 前端框架

vue自定义指令

vue自定义指令有全局注册和局部注册两种方式

  • 全局注册

    1
    Vue.directive(id, [definition])
  • 局部注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- 批量注册 -->
    import copy from './copy'
    import longpress from './longpress'
    // 自定义指令
    const directives = {
    copy,
    longpress,
    }

    export default {
    install(Vue) {
    Object.keys(directives).forEach((key) => {
    Vue.directive(key, directives[key])
    })
    },
    }
  • 注册之后使用在main.js

    1
    2
    3
    4
    <!-- main.js使用 -->
    import Vue from 'vue'
    import Directives from './directives/index'
    Vue.use(Directives)

指令函数中提供的几个钩子函数(可选):

  • bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作
  • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)
  • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值
  • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用
  • unbind: 只调用一次, 指令与元素解绑时调用

常使用的自定义指令

  • 防抖指令 v-debounce

    • 防止按钮在短时间内被多次点击,使用防抖函数限制规定时间内只能点击一次
      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
      const debounce = {
      inserted: function (el, binding) {
      let timer;
      <!-- keyup -->
      el.addEventListener('click', () => {
      if (timer) {
      clearTimeout(timer)
      }
      timer = setTimeout(() => {
      binding.value()
      }, 1000)
      })
      },
      }
      export default debounce;

      <!-- dom中使用 -->
      <template>
      <button v-debounce="debounceClick">防抖</button>
      </template>

      <script>
      export default {
      methods: {
      debounceClick () {
      console.log('只触发一次')
      }
      }
      }
      </script>
  • 实现一个图片懒加载指令,只加载浏览器可见区域的图片

    • 实现一个图片懒加载指令,只加载浏览器可见区域的图片。
      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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      const LazyLoad = {
      // install方法
      install(Vue, options) {
      const defaultSrc = options.default
      Vue.directive('lazy', {
      bind(el, binding) {
      LazyLoad.init(el, binding.value, defaultSrc)
      },
      inserted(el) {
      if (IntersectionObserver) {
      LazyLoad.observe(el)
      } else {
      LazyLoad.listenerScroll(el)
      }
      },
      })
      },
      // 初始化
      init(el, val, def) {
      el.setAttribute('src', val)
      el.setAttribute('src', def)
      },
      // 利用IntersectionObserver监听el
      observe(el) {
      var io = new IntersectionObserver((entries) => {
      const realSrc = el.dataset.src
      if (entries[0].isIntersecting) {
      if (realSrc) {
      el.src = realSrc
      el.removeAttribute('src')
      }
      }
      })
      io.observe(el)
      },
      // 监听scroll事件
      listenerScroll(el) {
      const handler = LazyLoad.throttle(LazyLoad.load, 300)
      LazyLoad.load(el)
      window.addEventListener('scroll', () => {
      handler(el)
      })
      },
      // 加载真实图片
      load(el) {
      const windowHeight = document.documentElement.clientHeight
      const elTop = el.getBoundingClientRect().top
      const elBtm = el.getBoundingClientRect().bottom
      const realSrc = el.dataset.src
      if (elTop - windowHeight < 0 && elBtm > 0) {
      if (realSrc) {
      el.src = realSrc
      el.removeAttribute('src')
      }
      }
      },
      // 节流
      throttle(fn, delay) {
      let timer
      let prevTime
      return function (...args) {
      const currTime = Date.now()
      const context = this
      if (!prevTime) prevTime = currTime
      clearTimeout(timer)

      if (currTime - prevTime > delay) {
      prevTime = currTime
      fn.apply(context, args)
      clearTimeout(timer)
      return
      }

      timer = setTimeout(function () {
      prevTime = Date.now()
      timer = null
      fn.apply(context, args)
      }, delay)
      }
      },
      }

      export default LazyLoad;
      <!-- dom中使用 -->
      <img v-LazyLoad="xxx.jpg" />

分享8个非常实用的Vue自定义指令

react钩子

发表于 2020-12-28 | 分类于 前端框架

react钩子(hooks)

react组件

  • React 的核心是组件。v16.8 版本之前,组件的标准写法是类(class)
  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性
  • 任何一个组件都可以用类和钩子来写
  • 类

    1
    2
    3
    4
    5
    class Welcome extends React.Component {
    render() {
    return <h1>Hello, {this.props.name}</h1>;
    }
    }
  • 钩子

    1
    2
    3
    function Welcome(props) {
    return <h1>Hello, {props.name}</h1>;
    }
  • 大部分看到上面两段代码都会选择使用钩子,更简洁、代码量少、用起来也较轻,也更符合react函数式都本质

    • 类有很多强制性的语法约束、不易混乱,更适合初学者
    • 真实的 React App 由多个类按照层级,一层层构成,复杂度成倍增长。再加入 Redux,就变得更复杂
  • 而类就比较重,官方也推荐使用钩子(函数),而不是类
    • 钩子的灵活性比较大
    • react 团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。 组件的最佳写法应该是函数,而不是类

类、函数的差异

  • 毕竟写法不同,还是存在不同的编程方法论
  • 类是数据和逻辑的封装,即组件的状态和操作方法是封装在一起的
  • 函数一般来说,只应该做一件事,就是返回一个值,如:react 的函数组件只应该做一件事情=》返回组件的 HTML 代码,而没有其他的功能
  • 这种只进行单纯的数据计算(换算)的函数,在函数式编程里面称为 “纯函数”(pure function)

副效应

  • 函数式编程将那些跟数据计算无关的操作,都称为 “副效应” (side effect) ,
  • 如果函数内部直接包含产生副效应的操作,就不再是纯函数了,我们称之为不纯的函数,如(生成日志、储存数据、改变应用状态等等)
  • 纯函数内部只有通过间接的手段(即通过其他函数调用),才能包含副效应,如(钩子)

钩子(hook)

  • react函数组件的副效应解决方案,用来为函数组件引入副效应. 函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副效应)都必须通过钩子引入
  • React 为许多常见的操作(副效应),都提供了专用的钩子。

    • useState():保存状态
    • useContext():保存上下文(共享状态钩子)

      • 组件之间共享状态,可以使用useContext()
      • 使用 React Context API,在组件外部建立一个 Context

        1
        const AppContext = React.createContext({});
      • AppContext.Provider提供了一个 Context 对象,这个对象可以被子组件共享

      • useContext()钩子函数用来引入 Context 对象,从中获取username属性
    • useRef():保存引用
    • useReducer():action 钩子

      • React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux
      • Redux 的核心概念是,组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState
      • useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个值是状态的当前值,第二个值是发送 action 的dispatch函数

        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
        import React, { useContext } from 'react';
        import ReactDOM from 'react-dom';
        const [state, dispatch] = useReducer(reducer, initialState);
        const myReducer = (state, action) => {
        switch(action.type) {
        case('countUp'):
        return {
        ...state,
        count: state.count + 1
        }
        default:
        return state;
        }
        }
        function App() {
        const [state, dispatch] = useReducer(myReducer, { count: 0 });
        return (
        <div className="App">
        <button onClick={() => dispatch({ type: 'countUp' })}>
        +1
        </button>
        <p>Count: {state.count}</p>
        </div>
        );
        }
      • Hooks 可以提供共享状态和 Reducer 函数,所以它在这些方面可以取代 Redux。但是,它没法提供中间件(middleware)和时间旅行(time travel),如果你需要这两个功能,还是要用 Redux

    • 以上钩子都是引入某种特定的副效应,而 useEffect()是通用的副效应钩
    • ……

useEffect() 的用法

  • useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合
  • useEffect()本身是一个函数,由 React 框架提供,在函数组件内部调用即可
  • useEffect()的作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React, { useEffect } from 'react';

    function Welcome(props) {
    let [count, setCount] = useState(1); // 数组解构
    // 以上代码等价于
    // let countStateVariable = React.useState(1); // 返回一个有两个元素的数组
    // let count = countStateVariable[0]; // 数组里的第一个值 当前的state
    // let setCount = countStateVariable[1]; // 数组里的第二个值 更新state的函数
    // 使用 [0] 和 [1] 来访问有点令人困惑,因为它们有特定的含义。这就是我们使用数组解构的原因。

    useEffect(() => {
    document.title = `you click ${count} times`;
    },)
    return <div>
    <h1>hello, { props.title },{count}</h1>
    <button onClick={() => setCount(count + 1)}>累加</button>
    </div>;
    }
  • useEffect() 的第二个参数,不希望useEffect()每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染

    1
    2
    3
    4
    5
    6
    function Welcome(props) {
    useEffect(() => {
    document.title = `Hello, ${props.name}`;
    }, [props.name]);
    return <h1>Hello, {props.name}</h1>;
    }

useEffect() 的用途

  • 只要是副效应,都可以使用useEffect()引入。它的常见用途有下面几种。
    • 获取数据(data fetching)
    • 事件监听或订阅(setting up a subscription)
    • 改变 DOM(changing the DOM)
    • 输出日志(logging)

useEffect() 的返回值

  • 副效应是随着组件加载而发生的,那么组件卸载时,可能需要清理这些副效应
  • useEffect()允许返回一个函数,在组件卸载时,执行该函数,清理副效应。如果不需要清理副效应,useEffect()就不用返回任何值
  • useEffect()在组件加载时订阅了一个事件,并且返回一个清理函数,在组件卸载时取消订阅。
    1
    2
    3
    4
    5
    6
    useEffect(() => {
    const subscription = props.source.subscribe();
    return () => {
    subscription.unsubscribe();
    };
    }, [props.source]);

useEffect() 的注意点

  • 了解Hook规则
  • 使用useEffect()时,有一点需要注意。如果有多个副效应,应该调用多个useEffect(),而不应该合并写在一起
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    function App() {
    const [varA, setVarA] = useState(0);
    const [varB, setVarB] = useState(0);

    useEffect(() => {
    const timeout = setTimeout(() => setVarA(varA + 1), 1000);
    return () => clearTimeout(timeout);
    }, [varA]);

    useEffect(() => {
    const timeout = setTimeout(() => setVarB(varB + 2), 2000);

    return () => clearTimeout(timeout);
    }, [varB]);

    return <span>{varA}, {varB}</span>;
    }

learn more…

learn more…

code

react原理

发表于 2020-12-23 | 分类于 前端框架

react原理剖析

核心API

  • React.createElement:创建虚拟的DOM
  • React.Component: 实现自定义组件
  • ReactDOM.render: 渲染真实DOM
  • react-dom:主要是render逻辑

JSX是什么

  • 在线JSX预编译
  • react使用JSX来替代常规的JavaScript,JSX是一个看起来很像XML的JavaScript语法扩展

    为什么需要JXS

  • JSX执行更快,因为它在编译为JavaScript代码后进行了优化(在react里面可以用它描述视图)
  • 它是类型安全的,在编译过程中就能发现错误(编译器可以对它进行规范处理、一系列严谨转换、类型检测)
  • 使用JSX编写模版更加简单快捷(开发效率)

    JXS怎么用

  • 原理:babel-loader会预编译JSX为React.createElement(…)

setState

  • class组件的特点,就是拥有特殊状态并且可以通过setState更新状态,从而重新渲染视图,是学习React中最重要的api
  • setState并没有直接操作去渲染,而是执行了一个异步的updater队列,使用一个类来专门管理

虚拟dom

  • 用JavaScript对象表示DOM信息和结构,当状态变更的时候,重新渲染这个JavaScript的对象结构。这个JavaScript对象称为virtual dom
  • 简单的说虚拟dom就是js对象,可以描述dom
    Image text

diff算法

  • diff策略
    • 同级比较,Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
    • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。例如:div->p, CompA->CompB
    • 对于同一层级的一组子节点,通过唯一的key进行区分
  • 基于以上三个前提策略,React 分别对 tree diff、component diff 以及 element diff 进行算法优化,事实也证明,这三个前提策略是合理且准确的,它保证了整体界面构建的性能
  • element diff 差异类型:
    • 替换原来的节点,例如把div换成了p,Comp1换成Comp2
    • 移动、删除、新增子节点, 例如ul中的多个子节点li中出现了顺序互换
    • 修改了节点的属性,例如节点类名发生了变化
    • 对于文本节点,文本内容可能会改变
  • 重排(reorder)操作:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)
    • INSERT_MARKUP,新的 component 类型不在老集合里, 即是全新的节点,需要对新节点执行插入操作
    • MOVE_EXISTING,在老集合有新 component 类型,且 element 是可更新的类型,generateComponentChildren 已调用 receiveComponent,这种情况下 prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点
    • REMOVE_NODE,老 component 类型,在新集合里也有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作
  • 为什么需要虚拟dom和对diff算法理解
    • 抽象出虚拟dom层,减少重布局、重排、重绘次数,不直接更新真实dom
    • 比较出应不应该更新dom,即使更新,尽可能的一次性、批量的更新

react生命周期

发表于 2020-12-22 | 分类于 前端框架

react生命周期 图谱

react v16.0前的生命周期

  • 大部分团队不见得会跟进升到16版本,所以16前的生命周期还是很有必要掌握的,何况16也是基于之前的修改
  • 第一组件初始化(initialization)阶段

    • 类的构造方法( constructor() ),Test类继承了react Component这个基类,即继承这个react的基类,才能有render(),生命周期等方法可以使用, 也是函数组件不能使用这些方法的原因。
    • super(props) 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,供子组件读取(组件中props只读不可变,state可变)。 而 constructor() 用来做一些组件的初始化工作,如定义this.state的初始内容
      1
      2
      3
      4
      5
      6
      import React, { Component } from 'react';
      class Test extends Component {
      constructor(props) {
      super(props);
      }
      }
  • 第二组件的挂载(Mounting)阶段

    • 此阶段分为componentWillMount,render,componentDidMount三个时期
    • componentWillMount
      • 在组件挂载到DOM前调用,且只会被调用一次,在这边调用this.setState不会引起组件重新渲染,可把这里的内容提前到constructor()中,所以项目中很少用
    • render
      • 根据组件的props和state(无两者的重传递和重赋值,论值是否有变化,都可以引起组件重新render) ,return一个React元素(描述组件,即UI),不负责组件实际渲染工作,之后由React自身根据此元素去渲染出页面
        DOM。render是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有副作用),不能在里面执行this.setState,会有改变组件状态的副作用
    • componentDidMount
      • 组件挂载到DOM后调用,且只会被调用一次
  • 第三组件的更新(update)阶段

    • 此阶段分为componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate,render,componentDidUpdate
    • componentWillReceiveProps(nextProps)
      • 只调用于props引起的组件更新过程中,参数nextProps是父组件传给当前组件的新props。但父组件render方法的调用不能保证重传给当前组件的props是有变化的,所以在此方法中根据nextProps和this.props来查明重传的props是否改变,以及如果改变了要执行啥,比如根据新的props调用this.setState出发当前组件的重新render
    • shouldComponentUpdate(nextProps, nextState)
      • 通过比较nextProps,nextState及当前组件的this.props,this.state,返回true时当前组件将继续执行更新过程,返回false则当前组件更新停止,以此可用来减少组件的不必要渲染,优化组件性能
      • ps:这边也可以看出,就算componentWillReceiveProps()中执行了this.setState,更新了state,但在render前(如shouldComponentUpdate,componentWillUpdate),this.state依然指向更新前的state,不然nextState及当前组件的this.state的对比就一直是true了
    • componentWillUpdate(nextProps, nextState)
      • 在调用render方法前执行,在这边可执行一些组件更新发生前的工作,一般较少用
    • render
      • render方法在上文讲过,这边只是重新调用
    • componentDidUpdate(prevProps, prevState)
      • 在组件更新后被调用,可以操作组件更新的DOM,prevProps和prevState这两个参数指的是组件更新前的props和state
    • 了解此阶段前需要先明确下react组件更新机制。setState引起的state更新或父组件重新render引起的props更新,更新后的state和props相对之前无论是否有变化,都将引起子组件的重新render。详细可看这篇文章造成组件更新有两类(三种)情况:
      1.父组件重新render(父组件重新render引起子组件重新render的情况有两种)
      a. 直接使用,每当父组件重新render导致的重传props,子组件将直接跟着重新渲染,无论props是否有变化。可通过shouldComponentUpdate方法优化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      class Child extends Component { 
      shouldComponentUpdate(nextProps){ // 应该使用这个方法,否则无论props是否有变化都将会导致组件 跟着重新渲染
      if(nextProps.someThings === this.props.someThings) {
      return false;
      }
      }
      render() {
      return <div>{this.props.someThings}</div>
      }
      }

      b.在componentWillReceiveProps方法中,将props转换成自己的state

      • 函数(componentWillReceiveProps)中调用 this.setState() 将不会引起第二次渲染
      • componentWillReceiveProps中判断props是否变化了,若变化了,this.setState将引起state变化,从而引起render,此时就没必要再做第二次因重传props引起的render了,不然重复做一样的渲染了
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        class Child extends Component { 
        constructor(props) {
        super(props);
        this.state = {
        someThings: props.someThings
        };
        }
        componentWillReceiveProps(nextProps) { // 父组件重传props时就会调用这个方法
        this.setState({
        someThings: nextProps.someThings
        });
        }
        render() {
        return <div>{this.state.someThings}</div>
        }
        }

      2.组件本身调用setState,无论state有没有变化。可通过shouldComponentUpdate方法优化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      class Child extends Component { 
      constructor(props) {
      super(props);
      this.state = {
      someThings:1
      }
      }
      shouldComponentUpdate(nextStates) { // 应该使用这个方法,否则无论state是否有变化都将会导致组 件重新渲染
      if(nextStates.someThings === this.state.someThings) {
      return false;
      }
      }
      handleClick = () => { // 虽然调用了setState ,但state并无变化
      const preSomeThings = this.state.someThings;
      this.setState({
      someThings: preSomeThings
      })
      }
      render() {
      return <div onClick = {this.handleClick}>{this.state.someThings}</div>
      }
      }
  • 第四卸载阶段

    • 只有一个生命周期方法:componentWillUnmount
    • componentWillUnmount
      • 在组件被卸载前调用,可以在这里执行一些清理工作,比如清除组件中使用的定时器,清除componentDidMount中手动创建的DOM元素等,以避免引起内存泄漏

react v16.4 的生命周期

  • 变更缘由
    • 原来(React v16.0前)的生命周期在React v16推出的Fiber之后就不合适了,因为如果要开启async rendering, 在render函数之前的所有函数,都有可能被执行多次
  • 原来(React v16.0前)的生命周期有哪些是在render前执行的呢?
    • componentWillMount
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
  • 如果开发者开了async rendering,而且又在以上这些render前执行的生命周期方法做AJAX请求的话,那AJAX将被无谓地多次调用。明显不是我们期望的结果。而且在componentWillMount里发起AJAX,不管多快得到结果也赶不上首次render,而且componentWillMount在服务器端渲染也会被调用到(当然,也许这是预期的结果),这样的IO操作放在componentDidMount里更合适。禁止不能用比劝导开发者不要这样用的效果更好,所以除了shouldComponentUpdate,其他在render函数之前的所有函数(componentWillMount,componentWillReceiveProps,componentWillUpdate)都被getDerivedStateFromProps替代。也就是用一个静态函数getDerivedStateFromProps来取代被deprecate的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的stateReact v16.0刚推出的时候,是增加了一个componentDidCatch生命周期函数,这只是一个增量式修改,完全不影响原有生命周期函数;但是,到了React v16.3,大改动来了,引入了两个新的生命周期函数
  • 新引入了两个新的生命周期函数: getDerivedStateFromProps , getSnapshotBeforeUpdate
    • getDerivedStateFromProps
      • getDerivedStateFromProps 本来(React v16.3中)是只在创建和更新(由父组件引发部分),也就是不是不由父组件引发,那么getDerivedStateFromProps也不会被调用,如自身setState引发或者forceUpdate引发
  • React v16.3 这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps无论是Mounting还是Updating,也无论是因为什么引起的Updating,全部都会被调用,具体可看React v16.4 的生命周期图
  • React v16.4后的getDerivedStateFromProps
    • static getDerivedStateFromProps(props, state) 在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回null来不更新任何内容
  • getSnapshotBeforeUpdate
    • getSnapshotBeforeUpdate() 被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()
      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
      class ScrollingList extends React.Component { 
      constructor(props) {
      super(props);
      this.listRef = React.createRef();
      }
      getSnapshotBeforeUpdate(prevProps, prevState) { //我们是否要添加新的 items 到列表? // 捕捉滚动位置,以便我们可以稍后调整滚动
      if (prevProps.list.length < this.props.list.length) {
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
      }
      return null;
      }
      componentDidUpdate(prevProps, prevState, snapshot) {
      //如果我们有snapshot值, 我们已经添加了 新的items
      // 调整滚动以至于这些新的items 不会将旧items推出视图
      // (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
      if (snapshot !== null) {
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
      }
      }
      render() {
      return ( <div ref={this.listRef}>{/* ...contents... */}</div> );
      }
      }

react组件化

发表于 2020-12-21 | 分类于 前端框架

react组件化

使用antd的yarn create react-app antd-demo创建的项目,使用组件配置按需加载

  • 安装react-app-rewired取代react-scripts(package.json),可以扩展webpack的配置 ,类似vue.config.js
  • 项目根目录创建 config-overrides.js,配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const { injectBabelPlugin } = require("react-app-rewired");
    module.exports = function override(config, env) {
    config = injectBabelPlugin(
    // 在默认配置基础上注入
    // 插件名,插件配置
    ["import", { libraryName: "antd", libraryDirectory: "es", style: "css" }],
    config
    );
    // 高级组件、可使用装饰器配置 ES7 -> ES5
    config = injectBabelPlugin(
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    config
    );

    return config;
    };
  • 使用Button实例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React, { Component } from 'react'
    // antd.css都加载
    // import Button from 'antd/lib/button'
    // import 'antd/dist/antd.css'
    // 按需加载
    import {Button} from 'antd'

    export default class AntdTest extends Component {
    render() {
    return (
    <div>
    <Button type="primary">按钮</Button>
    </div>
    )
    }
    }

容器组件、函数组件,react渲染数据的深浅比较

  • 容器组件负责数据获取,展示组件负责根据props显示信息
  • shouldComponentUpdate
  • PureComponent (定制了shouldComponentUpdate后的Component(浅比较))

    1
    class Comp extends React.PureComponent {}
  • memo高阶组件(React v16.6.0 之后的版本,可以使用 React.memo 让函数式的组件也有PureComponent的功能)

    1
    const Demo = React.memo(() => ( <div>{this.props.value || 'loading...' } </div> ));
  • 实例

    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    import React, { Component } from "react";
    import { func } from "prop-types";

    // 容器组件
    export default class CommentList extends Component {
    constructor(props) {
    super(props);
    this.state = {
    comments: []
    };
    }
    componentDidMount() {
    // 不断轮询比较、耗费🆓
    // setInterval(() => { // 每次生成的是全新数组
    setTimeout(() => {
    this.setState({
    comments: [
    { body: "react is very good", author: "facebook" },
    { body: "vue is very good", author: "youyuxi" }
    ]
    });
    }, 1000);
    }
    render() {
    return (
    <div>
    {this.state.comments.map((c, i) => (
    <Comment key={i} data={c} />
    // <Comment key={i} body={c.body} author={c.author} />
    // <Comment key={i} {...c} />
    ))}
    </div>
    );
    }
    }
    // 展示组件
    // memo高阶组件(也是一个函数,接收一个组件、返回一个全新的组件),也是浅比较(实现原理和使用PureComponent一样,解决了以前函数组件不能使用PureComponent)
    // React v16.6.0 之后的版本,可以使用 React.memo 让函数式的组件也有PureComponent的功能
    // const Comment = React.memo(function(props) {
    // console.log("render Comment"); // 执行两次
    // return (
    // <div>
    // <p>{props.body}</p>
    // <p> --- {props.author}</p>
    // </div>
    // );
    // });

    // 使用PureComponent (15.3 版本) 浅比较,避免对象比较(引用类型),挖掘太深,也没有达到效果
    // 解决 数据没有变 render函数不执行
    // 传值类型、或者避免引用类型的地址不变(尽量一层)
    // class Comment extends React.PureComponent{
    // render() {
    // console.log("render comment");
    // return (
    // <div>
    // <p>{this.props.body}</p>
    // <p> --- {this.props.author}</p>
    // </div>
    // );
    // }
    // }

    // class Comment extends React.Component{
    // // 生命周期进行比较 新状态nextProps 与 data比较
    // // 弊端: 比较累赘
    // shouldComponentUpdate(nextProps){
    // if (nextProps.data.body === this.props.data.body &&
    // nextProps.data.author === this.props.data.author) {
    // return false;
    // }
    // return true;
    // }

    // render() {
    // console.log("render comment"); // 执行了2次
    // return (
    // <div>
    // <p>{this.props.data.body}</p>
    // <p> --- {this.props.data.author}</p>
    // </div>
    // );
    // }
    // }

    function Comment({data}){
    console.log("render comment"); // 执行了4次
    return (
    <div>
    <p>{data.body}</p>
    <p> --- {data.author}</p>
    </div>
    );
    }

高阶组件

  • 在React里就有了HOC(Higher-Order Components)的概念
  • 高阶组件也是一个组件,但是他返回另外一个组件,产生新的组件可以对属性进行包装,甚至重写部分生命周期
    1
    2
    3
    4
    5
    6
    7
    const demoComp = (Component) => { 
    const NewComponent = (props) => {
    return <Component {...props} name='demo组件' />;
    };
    return NewComponent;
    };
    // 上面demoComp组件,其实就是代理了Component,只是多传递了一个name参数

高阶链式调用

  • 高阶组件最巧妙的一点,是可以链式调用

高阶组件装饰器写法

  • ES7装饰器可用于简化高阶组件写法
  • npm install –save-dev babel-plugin-transform-decorators-legacy
  • 同上,项目根目录创建 config-overrides.js,配置
  • 参考源码 https://github.com/yongfeng-peng/my-react-demo/blob/master/src/components/Hoc.js
    1
    2
    3
    4
    5
    // 高级组件、可使用装饰器配置 ES7 -> ES5
    config = injectBabelPlugin(
    ["@babel/plugin-proposal-decorators", { legacy: true }],
    config
    );

组件跨层级通信 - 上下文

  • 组件跨层级通信可使用Context
  • 这种模式下有两个角色,Provider和Consumer
  • Provider为外层组件,用来提供 数据;内部需要数据时用Consumer来读取
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const FormContext = React.createContext()
    const FormProvider = FormContext.Provider
    const FormConsumer = FormContext.Consumer
    let store = {
    name: '学习',
    sayHi() {
    console.log(this.name);
    }
    }
    let withForm = Component=> {
    const NewComponent = (props) => {
    return <FormProvider value={store}> <Component {...props} /> </FormProvider>
    };
    return NewComponent;
    }
    @withForm class App extends Component {
    render() {
    return <FormConsumer> {
    store=> {
    return <Button onClick={()=>store.sayHi()}> {store.name} </Button>
    }
    } </FormConsumer>
    }
    }

code

react

发表于 2020-12-19 | 分类于 前端框架

react

React和ReactDOM

  • React逻辑控制(义务控制、视图模型控制),React.createElement()(生成虚拟DOM,React生成的组件或者数据与render渲染的数据进行diff,打补丁)

JSX

  • 表达式:

    1
    {expr}
  • 属性:

    1
    <div id={}></div>
  • jsx也是表达式:

    1
    <p>{jsx}</p>

组件

  • 函数式:

    1
    2
    3
    function Comp(props) {
    return(...)
    }
  • 类:

    1
    2
    3
    4
    5
    class Comp extends React.Component {
    render() {
    return(...)
    }
    }

属性

1
<Comp name='' style={{...}}>

状态

1
2
3
4
5
6
7
8
9
class Comp {
state = {}
componentDidMount() {
this.setState({
prop: val, // 批量异步的,不会立刻生效
})
this.setState({state => (prop: val)})
}
}

条件和循环

1
2
3
{this.state.isLOgin ? <p>{userInfo.name}</p> : '登陆'}
{this.state.message && <p>{this.state.message}</p>}
{this.state.list.map(u => <li>{u.name}</li>)}

事件

1
2
3
4
5
6
7
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
} // 第二种
handleChange = () => {} // 第一种
<input onChange={this.handleChange} /> // this指向
<input onChange={() => this.handleChange(user)} /> // 第三种

通信

1
2
3
// 属性方式通信(父组件复杂,子组件负责调用)
// 父子组件隔代,上下文关系,及redux
<Comp titile={} onSubmit={this.onSubmit} />

React VScode开发快捷键

  • 按照扩展插件 ES7 React/Redux/GraphQL/React-Native snippets
  • 几个常用命令
  • rcc 快速穿件一个组件(使用extends方式)
  • rconst 快速创建一个 constuctor
  • rcep 快速创建一个组件(使用extends方式)
  • rcredux 快速创建一个 redux格式的类模板
  • clg 是 console.log()的快捷键
    To Learn More…

小程序中手机号识别

发表于 2020-12-14 | 分类于 小程序

小程序中手机号识别

后端返回字符串包好手机号,点击可拨打电话

  • split技巧
  • 注意的点是这个正则regPhone,里面加了有括号,如果separator是包含捕获括号的正则表达式(),则匹配结果包含在数组中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    let _str = "xxx,联系电话:18839687266。稍后与您联系。xxx。";
    parseStr (str) {
    const regPhone = /(1\d{10})/;
    const list = str.split(regPhone);
    const result = [];
    return list.map(item => {
    let tag;
    regPhone.test(item) ? tag = 'phone' : tag = 'text';
    return {
    type: tag,
    text: item
    }
    })
    }

google扩展程序

发表于 2020-12-03 | 分类于 tool

google扩展程序安装

vue-devtools工具的安装
  • 创建一个新的文件夹,名为vue-devtools
  • sudo npm install vue-devtools
  • 进入vender文件夹,打开manifest.json,进行编辑
  • persistent修改成true
  • 打开扩展程序,把刚刚修改的vender文件夹拖拽进来就可以了(记得开启开发者模式)
react-devtools工具的安装
  • git clone https://github.com/facebook/react-devtools
  • cd react-devtools
  • yarn install
  • yarn build:extension(具体命令,在package.json文件中的script下查找以下命令)
  • 打开扩展程序,把生成的shells/chrome/build/unpacked文件夹拖拽进来就可以了(记得开启开发者模式)
redux-devtools工具的安装
  • git clone https://github.com/zalmoxisus/redux-devtools-extension.git
  • npm i && npm run build:extension (npm install chromedriver –chromedriver_cdnurl=http://cdn.npm.taobao.org/dist/chromedriver)
  • 打开扩展程序,把生成的./build/extension文件夹添加到压缩程序(记得开启开发者模式)
其他好用扩展程序
  • WEB前端助手(FeHelper)
    • 一款国产的、超级实用的前端开发工具合集,之前扩展迷也用专文介绍过这款插件。
    • WEB前端助手(FeHelper)包含多个独立小应用,比如:Json工具、代码美化、代码压缩、二维码、Postman、markdown、网页油猴、便签笔记、信息加密与解密、随机密码生成、Crontab等等。
    • down…
1234…7
yongfeng.peng

yongfeng.peng

(女英

67 日志
13 分类
11 标签
© 2022 yongfeng.peng