Web全栈架构师(二)——React学习笔记(3)

React学习笔记

  • 项目实战
    • 资源
    • 页面布局
    • 用户登录认证
    • 商品列表
    • 加购物车
    • 界面
  • React原理
    • React核心API
    • JSX
    • 自定义实现React
      • kreact.js
      • kreact-dom.js
      • kvdom.js
      • 总结
    • PureComponent
    • setState
    • diff算法
      • diff策略
      • element diff

项目实战

资源

  • Ant Design Pro 使用:https://pro.ant.design/docs/getting-started-cn
  • Ant Design Pro 预览:https://preview.pro.ant.design/dashboard/analysis
  • antd 布局组件:https://ant.design/components/layout-cn/
  • 安装:npm install ant-design-pro --save

页面布局

  • 使用 antd 布局组件 Layout、菜单 Menu
  • 新建 src/layouts/index.js
import { Layout, Menu } from "antd";
import Link from "umi/link";
import styles from "./index.css";
import React, { Component } from "react";
import { Icon, Badge, Dropdown } from "antd";
import { connect } from "dva";

const { Header, Footer, Content } = Layout;

export default class extends Component {
  onItemClick = item => {
    console.log(item);
  };
  render() {
    const selectedKeys = [this.props.location.pathname];
    return (
      // 上中下布局
      <Layout>
        {/* 页头 */}
        <Header className={styles.header}>
          <img className={styles.logo} src="https://img.kaikeba.com/logo-new.png"/>
          <Menu theme="dark" mode="horizontal" selectedKeys={selectedKeys}
            style={{ lineHeight: "64px", float: "left" }}>
            <Menu.Item key="/">
              <Link to="/">商品</Link>
            </Menu.Item>
            <Menu.Item key="/users">
              <Link to="/users">用户</Link>
            </Menu.Item>
            <Menu.Item key="/about">
              <Link to="/about">关于</Link>
            </Menu.Item>
          </Menu>
          {/* 购物车信息 */}
          ...
        </Header>
        {/* 内容 */}
        <Content className={styles.content}>
          <div className={styles.box}>{this.props.children}</div>
        </Content>
        {/* 页脚 */}
        <Footer className={styles.footer}>开课吧</Footer>
      </Layout>
    );
  }
}

用户登录认证

Web全栈架构师(二)——React学习笔记(3)_第1张图片

  • 使用 ant-design-pro 的 Exception 组件构建404页面 src/pages/404.js
import {Exception} from 'ant-design-pro'
export default function() {
  return (
    <Exception type="404" backText="返回首页"></Exception>
  );
}
  • 使用 ant-design-pro 的 Login 组件构建登录页面 src/pages/login.js
import React, { Component } from "react";
import styles from "./login.css";

import { Login } from "ant-design-pro";
import { connect } from "dva";

const { UserName, Password, Submit } = Login;

@connect()
export default class extends Component {
  onSubmit = (err, values) => {
    console.log("用户输入:", values);
    if (!err) {
      // 校验通过,提交登录
      this.props.dispatch({ type: "user/login", payload: values });
    }
  };
  render() {
    return (
      <div className={styles.loginForm}>
        {/* logo */}
        <img className={styles.logo} src="https://img.kaikeba.com/logo-new.png"/>
        {/* 登录表单 */}
        <Login onSubmit={this.onSubmit}>
          <UserName name="username" placeholder="kaikeba" rules={[{ required: true, message: "请输入用户名" }]}/>
          <Password name="password" placeholder="123" rules={[{ required: true, message: "请输入密码" }]}/>
          <Submit>登录</Submit>
        </Login>
      </div>
    );
  }
}
  • 登录接口mock,创建 mock/login.js
export default {
  "post /api/login"(req, res, next) {
    const { username, password } = req.body;
    console.log(username, password);
    if (username == "kaikeba" && password == "123") {
      return res.json({
        code: 0,
        data: {
          token: "kaikebaisgood",
          role: "admin",
          balance: 1000,
          username: "kaikeba"
        }
      });
    }
    if (username == "jerry" && password == "123") {
      return res.json({
        code: 0,
        data: {
          token: "kaikebaisgood",
          role: "user",
          balance: 100,
          username: "jerry"
        }
      });
    }
    return res.status(401).json({
      code: -1,
      msg: "密码错误"
    });
  }
};
  • 用户信息保存和登录动作编写,创建src/models/user.js
import axios from "axios";
import router from "umi/router";

const userinfo = JSON.parse(localStorage.getItem("userinfo")) || {
  token: "",
  role: "",
  username: "",
  balance: 0
};

// api
function login(payload) {
  return axios.post("/api/login", payload);
}

export default {
  namespace: "user", // 可省略
  state: userinfo, // 初始状态:缓存或空对象
  effects: {
    // action: user/login
    *login({ payload }, { call, put }) {
      try {
        const {
          data: { code, data: userinfo }
        } = yield call(login, payload);
        if (code == 0) {
          // 登录成功: 缓存用户信息
          localStorage.setItem("userinfo", JSON.stringify(userinfo));
          yield put({ type: "init", payload: userinfo });
          router.push("/");
        } else {
          // 登录失败:弹出提示信息,可以通过响应拦截器实现
        }
      } catch (error) {}
    }
    // *logout({ payload }, { call, put }) {
    //   localStorage.removeItem("userinfo");
    //   yield put({ type: "clear" });
    //   router.push("/login");
    // }
  },
  reducers: {
    init(state, action) {
      // 覆盖旧状态
      return action.payload;
    }
    // clear(state, action) {
    //   return initUserinfo;
    // }
  }
};
  • 登录失败处理
    设置响应状态吗,mock/login.js
return res.status(401).json({
  code: -1,
  msg: "密码错误"
});

响应拦截,创建 src/interceptor.js

import axios from "axios";
import { notification } from "antd";

const codeMessage = {
  202: "一个请求已经进入后台排队(异步任务)。",
  401: "用户没有权限(令牌、用户名、密码错误)。",
  404: "发出的请求针对的是不存在的记录,服务器没有进行操作。",
  500: "服务器发生错误,请检查服务器。"
};

// 仅拦截异常状态响应
axios.interceptors.response.use(null, ({ response }) => {
  if (codeMessage[response.status]) {
    notification.error({
      message: `请求错误 ${response.status}: ${response.config.url}`,
      description: codeMessage[response.status]
    });
  }
  return Promise.reject(err);
});

拦截器生效,创建 src/global.js

import interceptor from './interceptor'

商品列表

  • 数据mock,mock/goods.js
// 课程列表
const course={
  data:{
    'Javascript':[
      {id:1,name:'ES6语法实战',img:'LearnES6_Final.png',price:'100',solded:'561',},
      {id:2,name:'Typescript实战',img:'Typescript_Plumbing_image.png',price:'100',solded:'156',},
      {id:3,name:'Javascript算法实战',img:'JSBasic-Algorithms_Final.png',price:'100',solded:'526',},
    ],
    'React':[
      {id:4,name:'React入门',img:'ReactBeginners.png',price:'100',solded:'536',},
      {id:5,name:'ReactNative开发自己的APP',img:'ReactNative.png',price:'100',solded:'456',},
      {id:6,name:'React服务端渲染实战',img:'ReactNextServer_Final.png',price:'100',solded:'556',},
      {id:7,name:'Redux Sage中间件实战',img:'ReduxSaga.png',price:'100',solded:'2256',},
      {id:8,name:'试用react开发PWA应用',img:'PWAReact_Final.png',price:'100',solded:'1156',},
      {id:9,name:'React Hooks实战',img:'SimplifyHooks_Final.png',price:'100',solded:'5361',},
      {id:10,name:'React Mobx状态管理实战',img:'React_Mobx_TS.png',price:'100',solded:'956',},
    ],
    'Vuejs':[
      {id:11,name:'Vue进阶实战',img:'VueJS_Final.png',price:'180',solded:'586',},
      {id:12,name:'Vuejs开发pwa应用',img:'VuePwa.png',price:'100',solded:'596',},
      {id:13,name:'试用TS开发Vuejs应用',img:'TSVue_Final.png',price:'100',solded:'526',},
    ],
    'Git':[
      {id:14,name:'Github从入门到精通',img:'github.png',price:'99',solded:'10',},
      {id:15,name:'Git版本控制实战',img:'LearnGit.png',price:'49',solded:'180',},],
    'Test':[
      {id:16,name:'Puppetee测试入门',img:'TestGooglePuppeteer_Final.png',price:'10',solded:'56',},
      {id:17,name:'使用jest测试你的React项目',img:'TestReactJest.png',price:'30',solded:'10',},
    ],
    'Python':[
      {id:18,name:'Python从入门到精通',img:'IntroPython.png',price:'100',solded:'56',},
    ],
    'Node.js':[
      {id:19,name:'使用Docker部署你的nodejs',img:'NodeDocker_1000.png',price:'100',solded:'56',},
      {id:20,name:'在AWS环境部署nodejs',img:'NodeAWSServerless_Final.png',price:'100',solded:'56',},
    ],
    'GraphQL':[
      {id:21,name:'GraphQL从入门到精通',img:'GraphQL_Final.png',price:'100',solded:'56',},
    ]
  },
  tags:['Javascript','React','Vuejs','Git','Test','Python','Node.js','GraphQL']
}
// 课程分类
course.tags.forEach(tag=>{
  course.data[tag].forEach(v=>{
    v.tag = tag
  })
})

export default {
  // "method url": Object 或 Array
  // "get /api/goods": {
  //   result: data
  // },
  // "method url": (req, res) => {}
  "get /api/goods": function(req, res, next) {
    setTimeout(() => {
      res.json({
        code: 0,
        data: course
      });
    }, 2500);
  }
};
  • 图片素材,public/courses/*.png
  • 修改商品数据模型 src/pages/goods/models/goods.js(局部模型):
import axios from "axios";

// api
function getGoods() {
  return axios.get("/api/goods");
}

export default {
  namespace: "goods",
  state: {
    courses: {}, // 课程
    tags: [] // 分类
  },
  effects: {
    *getList(action, { call, put }) {
      const { data: {data: courseData} } = yield call(getGoods);
      yield put({ type: "initGoods", payload: courseData });
    }
  },
  reducers: {
    initGoods(state, { payload }) {
      const { tags, data: courses } = payload;
      return { ...state, tags, courses };
    },
    addGood(state, action) {
      console.log(action);
      return [...state, { title: action.payload.title }];
    }
  }
};
  • 商品展示组件,创建src/pages/goods/index.js
import React, { Component } from "react";
import { Card, Row, Col, Icon, Skeleton } from "antd";
import { TagSelect } from "ant-design-pro";
import { connect } from "dva";

@connect(
  state => ({
    courses: state.goods.courses,
    tags: state.goods.tags,
    // loading: state.loading
  }),
  {
    addCart: item => ({type: "cart/addCart",payload: item}),
    getList: () => ({type: "goods/getList"})
  }
)
class Goods extends Component {
  constructor(props) {
    super(props);
    // displayCourses为需要显示的商品数组
    this.state = {
      tags: [], // 默认未选中
      displayCourses: new Array(8).fill({}) // 设置size可用于骨架屏展示
    };
  }
  componentDidMount() {
    this.props.getList();
  }
  componentWillReceiveProps(props) {
    // 数据传入时执行一次tagSelectChange
    if (props.tags.length) {
      this.tagSelectChange(props.tags, props.courses);
    }
  }
  tagSelectChange = (tags, courses = this.props.courses) => {
    // 过滤出显示数据
    let displayCourses = [];
    tags.forEach(tag => {
      displayCourses = [...displayCourses, ...courses[tag]];
    });
    this.setState({ displayCourses, tags });
  };

  addCart = (e, item) => {
    e.stopPropagation();
    this.props.addCart(item);
  };

  render() {
    // if (this.props.loading.models.goods) {
    //   return 
加载中...
;
// } return ( <div> {/* 分类标签 */} ... {/* 商品列表 */} ... </div> ); } } export default Goods;
  • 分类标签
<TagSelect onChange={this.tagSelectChange} value={this.state.tags}>
  {this.props.tags.map(tag => {
    return (
      <TagSelect.Option key={tag} value={tag}>{tag}</TagSelect.Option>
    );
  })}
</TagSelect>
  • 商品列表
<Row type="flex" justify="start">
  {this.state.displayCourses.map((item, index) => {
    return (
      // span=6表示4列
      <Col key={index} style={{ padding: 8 }} span={6}>
        {item.name 
        ? (<Card extra={<Icon onClick={e => this.addCart(e, item)} type="shopping-cart" style={{ fontSize: 18 }}/>}
            onClick={() => this.toDetail(item)} hoverable  title={item.name} cover={<img src={"/course/" + item.img} />} >
            <Card.Meta description={
                <div>
                  <span>{item.price}</span>
                  <span style={{ float: "right" }}>
                    <Icon type="user" /> {item.solded}
                  </span>
                </div>
              } />
            <div />
          </Card>) 
        : (<Skeleton active={true} />)}
      </Col>
    );
  })}
</Row>

加购物车

  • 创建 models/cart.js
export default {
  namespace: "cart", // 可省略
  state: JSON.parse(localStorage.getItem("cart")) || [], // 初始状态:缓存或空数组
  reducers: {
    addCart(cart, action) {
      const good = action.payload;
      const idx = cart.findIndex(v => v.id == good.id);
      if (idx > -1) {
        // 更新数量
        const cartCopy = [...cart];
        const itemCopy = { ...cartCopy[idx] };
        itemCopy.count += 1;
        cartCopy.splice(idx, 1, itemCopy);
        return cartCopy;
      } else {
        // 新增
        return [...cart, { ...good, count: 1 }];
      }
    }
  }
};
  • 购物车数据同步的到localStorage,创建 src/app.js,配置 dva
export const dva = {
  config: {
    onStateChange(state,a) {
      if (localStorage) {
        localStorage.setItem("cart", JSON.stringify(state.cart));
      }
    }
  }
};
  • 表头添加购物车信息,src/layouts/index.js:
...
export default class extends Component {
  ...
  render() {
    ....
    return (
      // 上中下布局
      <Layout>
        {/* 页头 */}
        <Header className={styles.header}>
          ...
          {/* 购物车信息 */}
          <Dropdown overlay={menu} placement="bottomRight">
            <div className={styles.cart}>
              <Icon type="shopping-cart" style={{ fontSize: 18 }} />
              <span>我的购物车</span>
              <Badge count={this.props.count} offset={[-4, -18]} />
            </div>
          </Dropdown>
        </Header>
        {/* 内容 */}
        ...
        {/* 页脚 */}
        ...
      </Layout>
    );
  }
}
  • 展示购物车数据
@connect(state => ({
  count: state.cart.length,
  cart: state.cart
}))
export default class extends Component {
	const menu = (
	  <Menu>
	    {this.props.cart.map((item, index) => (
	      <Menu.Item key={index}>
	        {item.name}×{item.count} <span>{item.count * item.price}</span>
	      </Menu.Item>
	    ))}
	  </Menu>
	);
	render(){
		return (
			...
			{/* 购物车信息 */}
	          <Dropdown overlay={menu} placement="bottomRight">
	            ...
	          </Dropdown>
			...
		);
	}
}

界面

Web全栈架构师(二)——React学习笔记(3)_第2张图片

React原理

React核心API

React源码:https://github.com/facebook/react/blob/master/packages/react/src/React.js

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  error,
  warn,

  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,

  Fragment: REACT_FRAGMENT_TYPE,
  Profiler: REACT_PROFILER_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,

  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};

// Note: some APIs are added with feature flags.
// Make sure that stable builds for open source
// don't modify the React object to avoid deopts.
// Also let's not expose their names in stable builds.

if (enableStableConcurrentModeAPIs) {
  React.ConcurrentMode = REACT_CONCURRENT_MODE_TYPE;
  React.unstable_ConcurrentMode = undefined;
}

export default React;
  • React-dom:https://github.com/facebook/react/blob/master/packages/react-dom/src/client/ReactDOM.js
  • 核心精简API:
let React = {
	createElement,
	Component,
	PureComponent
}

JSX

为什么需要js?

JSX是对 js 语法的扩展,使我们可以用类似 xml 方式描述视图。

为什么需要JSX?

执行快、类型安全、简单快速-提高开发效率

  • 原理babel-loader 会预编译 JSX 为 React.createElement(type, props, …children)
  • 使用:
import React from 'react'
import ReactDOM from 'react-dom'

function Comp(props){
  return <h2>hi, {props.name}</h2>
}
const jsx = (
  <div id="demo">
    <span>hi</span>
    <Comp name="kaikeba"></Comp>
  </div>
)
console.log(JSON.stringify(jsx, null, 2))
ReactDOM.render(jsx, document.querySelector('#root'))
  • 打印出 jsx 查看:
{
  "type": "div",
  "key": null,
  "ref": null,
  "props": {
    "id": "demo",
    "children": [
      {
        "type": "span",
        "key": null,
        "ref": null,
        "props": {
          "children": "hi"
        },
        "_owner": null,
        "_store": {}
      },
      {
        "key": null,
        "ref": null,
        "props": {
          "name": "kaikeba"
        },
        "_owner": null,
        "_store": {}
      }
    ]
  },
  "_owner": null,
  "_store": {}
}

自定义实现React

  • src/index.js
// import React from 'react'
import React, {Component} from './kreact'
// import ReactDOM from 'react-dom'
import ReactDOM from './kreact-dom'

// 函数组件
function CompFunc(props){
  return <h2>function组件 hi, {props.name}</h2>
}
// class组件
class CompClass extends Component {
  render() {
    return <h2>class组件</h2>
  }
}
const users = [
  {id: 1, name: 'jerry'},
  {id: 2, name: 'tom'}
];
const jsx = (
  <div id="demo" style="color: red" className="box">
    <span>hi</span>
    <CompFunc name="kaikeba"></CompFunc>
    <CompClass></CompClass>
    <ul>
      {users.map(u => (<li key={u.id}>{u.name}</li>))}
    </ul>
  </div>
)
console.log(jsx)
// ReactDOM.render(

React 原理

, document.querySelector('#root'))
ReactDOM.render(jsx, document.querySelector('#root'))

kreact.js

  • kreact 主要用于核心 API 声明
  • createElement 方法实现
import {createVNode} from './kvdom'

function createElement(type, props, ...children){
  // console.log(arguments)
  // 传递的类型有三种:1-原生标签,2-函数式组件,3-class组件
  // 使用 vtype 属性表示元素类型
  props.children = children
  console.log({type, props})
  delete props.__source
  delete props.__self

  // 判断组件类型
  let vtype;
  if(typeof type === 'string'){
    // 原生标签,div、span、...
    vtype = 1;
  }else if(typeof type === 'function'){
    if(type.isClassComponent){
      // 类组件
      vtype = 2;
    } else{
      // 函数组件
      vtype = 3;
    }
  }

  return createVNode(vtype, type, props)
}
// 实现Component
export class Component {
  // 区分function和class组件
  static isClassComponent = true;
  constructor(props){
    this.props = props;
    this.state = {};
  }
  setState(state){
    // ...
  }
}
export default {
  createElement
}

kreact-dom.js

  • kreact-dom执行渲染逻辑
  • render 方法实现
import { initVNode } from "./kvdom";

function render(vnode, container) {
  // container.innerHTML = `
${JSON.stringify(vnode, null, 2)}
`
container.appendChild(initVNode(vnode)) } export default { render }

kvdom.js

  • kvdom创建dom、转换vdom、执行diff等
// 转换 vdom 为 dom
export function initVNode(vnode){
  // vnode是一颗虚拟DOM树
  const {vtype} = vnode
  if(!vtype){
    // 文本节点,TextNode
    return document.createTextNode(vnode);
  }
  if(vtype === 1){
    // 原生节点
    return createElement(vnode);
  }else if(vtype === 2) {
    // class 组件
    return createClassComp(vnode);
  }else{
    return createFunctionComp(vnode);
  }
}
function createElement(vnode){
  const {type, props} = vnode;
  const node = document.createElement(type);
  // 属性处理
  const {key, children, ...rest} = props;
  Object.keys(rest).forEach(attr => {
    // 特殊处理的属性:htmlFor,className
    if(attr === 'className'){
      node.setAttribute('class', rest[attr])
    }else{
      node.setAttribute(attr, rest[attr])
    }
  });
  // 递归处理可能存在的子元素
  children.forEach(c => {
    // c 如果是数组
    if(Array.isArray(c)){
      c.forEach(n => {
        node.appendChild(initVNode(n))
      });
    }else{
      node.appendChild(initVNode(c))
    }
  });
  return node;
}
function createClassComp(vnode){
  const {type, props} = vnode;
  console.log(type)
  const component = new type(props);
  // 执行 class 组件的render方法得到vdom
  const vdom = component.render();
  return initVNode(vdom)
}
function createFunctionComp(vnode){
  const {type, props} = vnode;
  // type 是函数组件,它执行直接返回vdom
  const vdom = type(props);
  return initVNode(vdom)
}

// vdom diff
// ...

export function createVNode(vtype, type, props){
  // 传递的类型有三种:1-原生标签,2-函数式组件,3-class组件
  // 使用 vtype 属性表示元素类型
  const vnode = {
    vtype, type, props
  }
  return vnode
}

总结

  • webpack + babel-loader编译时,替换JSX为React.createElement(…)
  • 所有的React.createElement(…)执行结束后会得到一个JS对象树,他能完整描述 DOM 结构,称之为虚拟DOM
  • ReactDOM.render(vdom, container)可以将vdom转换为dom,追加至container。通过遍历vdom树,根据vtype不同,执行不同的逻辑:vtype=1生成原生标签,vtype=2实例化class组件并将其render返回的vdom初始化,vtype=3直接执行函数将结果初始化

PureComponent

  • 继承Component,主要是设置了shouldComponentUpdate生命周期
import shallowEqual from './shallowEqual'
import Component from './Component'

export default function PureComponent(props, context){
	Component.call(this, props, context)
}
PureComponent.prototype = Object.create(Component.prototype)
PureComponent.prototype.constructor = PureComponent
PureComponent.prototype.isPureReactComponent = true
PureComponent.prototype.shouldComponentUpdate = shallowCompare
function shallowCompare(nextProps, nextState){
	return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState)
}

setState

  • class 的特点,就是可以 setState,使用如下:
class App extends React.Component {
	constructor(props){
		super(props)
		this.state = {num: 1}
	}
	componentDidMount(){
		setInterval(() => {
			this.setState({num: this.state.num + 1})
		}, 1000)
	}
	render(){
		return <div>
			<h2>你好,{this.props.name}</h2>{this.state.num}
		<div>
	}
}
  • setState并没有直接操作去渲染,而是执行了一个异步的 updater 队列,使用一个类来专门管理
export let updateQueue = {
  updaters: [],
  isPending: false,
  add(updater) {
    _.addItem(this.updaters, updater)
  },
  batchUpdate(){
    if(this.isPending){
      return
    }
    this.isPending = true
    let { updaters } = this
    let updater
    while (updater = updaters.pop()) {
      updater.updateComponent()
    }
    this.isPending = false
  }
}
function Updater(instance){
  this.instance = instance
  this.pendingStates = []
  this.pendingCallbacks = []
  this.isPending = false
  this.nextProps = this.nextContext = null
  this.clearCallbacks = this.clearCallbacks.bind(this)
}
Updater.prototype = {
  emitUpdate(nextProps, nextContext) {
    this.nextProps = nextProps
    this.nextContext = nextContext
    nextProps || !updateQueue.isPending
      ? this.updateComponent()
      : updateQueue.add(this)
  },
  updateComponent() {
    let {instance, pendingStates, nextProps, nextContext} = this
    if(nextProps || pendingStates.length>0){
      nextProps = nextProps || instance.props
      nextContext = nextContext || instance.context
      this.nextProps = this.nextContext = null
      shouldUpdate(instance, nextProps, this.getState(), nextContext, this.clearCallbacks)
    }
  },
  addState(nextState){
    if(nextState){
      _.addItem(this.pendingStates, nextState)
      if(!this.isPending){
        this.emitUpdate()
      }
    }
  },
  replaceState(nextState){
    let {pendingStates} = this
    pendingStates.pop()
    _.addItem(pendingStates, [nextState])
  },
  getState(){
    let {instance, pendingStates} = this
    let {state, props} = instance
    if(pendingStates.length){
      state = _.extend({}, state)
      pendingStates.forEach(nextState => {
        let isReplace = _.isArr(nextState)
        if(isReplace){
          nextState = nextState[0]
        }
        if(_.isFn(nextState)){
          nextState = nextState.call(instance, state, props)
        }
        if(isReplace){
          state = _.extend({}, nextState)
        }else{
          _.extend(state, nextState)
        }
      })
      pendingStates.length = 0
    }
    return state
  },
  clearCallbacks(){
    let {pendingCallbacks, instance} = this
    if(pendingCallbacks.length >0){
      this.pendingCallbacks = []
      pendingCallbacks.forEach(callback => callback.call(instance))
    }
  },
  addCallback(callback){
    if(_.isFn(callback)){
      _.addItem(this.pendingCallbacks, callback)
    }
  }
}

diff算法

Web全栈架构师(二)——React学习笔记(3)_第3张图片

diff策略

  • Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  • 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分
  • 基于以上三个前提策略,React分别对 tree diffcomponent diff 以及 element diff 进行了算法优化,事实也证明这三个前提策略是合理且准确的,它保证了整体界面构建的性能

element diff

当节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)MOVE_EXISTING(移动)、和REMOVE_NODE(删除)

  • INSERT_MARKUP:新的 component 类型不在老集合里,即是全新的节点,需要对新节点执行插入操作
  • MOVE_EXISTING:在老集合里有新 component 类型,且 element 是可更新的类型,generateComponentChildren已调用receiveComponent,这种情况下prevChild=nextChild,就需要做移动操作,可以复用以前的 DOM 节点
  • REMOVE_NODE:老 component 类型在新集合里有,但对应的 element 不同则不能直接复用和更新,需要执行删除操作,或者老 component 不在新集合里的,也需要执行删除操作
    Web全栈架构师(二)——React学习笔记(3)_第4张图片
    Web全栈架构师(二)——React学习笔记(3)_第5张图片
    Web全栈架构师(二)——React学习笔记(3)_第6张图片

你可能感兴趣的:(前端框架)