阿健666

阿健666 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 abc.com 编辑
编辑

前端产品小白

个人动态

阿健666 关注了专栏 · 5月8日

有赞美业前端团队

关注 2735

阿健666 收藏了文章 · 2019-12-06

MongoDB 可视化工具 Studio 3T 破解教程(Mac 版)

1. 下载安装包

因为破解包是针对 studio 3T 2019.3.0 版本的,这里提供对应版本的 studio 3T 安装包的下载地址:
https://download.studio3t.com...
(感谢评论里的小伙伴提供的下载地址)

2. 下载破解包

下载 data-man-mongodb-ent-2019.3.0.jar 包。

3. 替换包

打开 Studio 3T 安装目录:
image.png
将下载好的包拷贝到安装目录,替换掉原来的包。
image.png

4. 运行终端命令

在终端执行以下命令:

sudo spctl --master-disable

image.png
输入电脑开机密码后回车即可。

5. 完成

绕过了许可证测试,这意味着 Studio 3T 可以无限期地使用。但是 Pro 和 Enterprise 企业功能没有被修改。
打开软件看到如下界面即表示破解成功。
image.png

查看原文

阿健666 提出了问题 · 2019-11-24

微信小程序升级后报错Please do not register multiple Pages,如何解决?

mpvue升级从1.0到1.4版本,微信小程序报错,微信开发工具版本号为:1.02.1911181

VM219 WAService.js:1 Uncaught Error: APP-SERVICE-Engine:Please do not register multiple Pages in pages/personCenter/uploadPhotos/main.js

image.png

问题排查:
检查后没有发现重复注册的页面

关注 2 回答 1

阿健666 赞了问题 · 2019-11-19

解决前后端分离,前端如何判断登录状态?

首页有个链接链到A页面,但是A页面只有登录用户才有权限查看,这个时候前端怎么判断用户是否已经登录?如果已经登录了,就正常跳转到A页面,否则跳转到登录页面。也有其他的js逻辑需要判断isLogin的情况。难道要专门搞个接口去查?感觉这样肯定不对。

之前处理的方式是登录成功之后前端在cookie或localStorage里面保存一个isLogin字段,在需要确认是否登录的时候从这里取值判断。但这与后端判断是否登录的方式就不一致了,实际上也应该以后端的判断为准。有次面试,面试官也说了前端自己保存isLogin的方式不对(当时傻缺了,我应该立马反问、请教面试官该如何处理...)

各位大神,请指教啊,这个问题困扰好久了。由这个问题展开来应该就是整个登录注册系统的设计了,老司机带带我,感激不尽,多谢!


补充:那次面试,面试官说像这种登录注册系统都是有很完善的系统了,让我回去自己搜搜。看过一些文章,但具体操作还不是很清楚,所以想请大神们讲一讲或者是说一下自己项目中采用的方法,感谢!

关注 17 回答 7

阿健666 收藏了问题 · 2019-11-19

前后端分离,前端如何判断登录状态?

首页有个链接链到A页面,但是A页面只有登录用户才有权限查看,这个时候前端怎么判断用户是否已经登录?如果已经登录了,就正常跳转到A页面,否则跳转到登录页面。也有其他的js逻辑需要判断isLogin的情况。难道要专门搞个接口去查?感觉这样肯定不对。

之前处理的方式是登录成功之后前端在cookie或localStorage里面保存一个isLogin字段,在需要确认是否登录的时候从这里取值判断。但这与后端判断是否登录的方式就不一致了,实际上也应该以后端的判断为准。有次面试,面试官也说了前端自己保存isLogin的方式不对(当时傻缺了,我应该立马反问、请教面试官该如何处理...)

各位大神,请指教啊,这个问题困扰好久了。由这个问题展开来应该就是整个登录注册系统的设计了,老司机带带我,感激不尽,多谢!


补充:那次面试,面试官说像这种登录注册系统都是有很完善的系统了,让我回去自己搜搜。看过一些文章,但具体操作还不是很清楚,所以想请大神们讲一讲或者是说一下自己项目中采用的方法,感谢!

阿健666 收藏了文章 · 2019-10-07

React进阶——使用高阶组件(Higher-order Components)优化你的代码

什么是高阶组件

Higher-Order Components (HOCs) are JavaScript functions which add functionality to existing component classes.

通过函数向现有组件类添加逻辑,就是高阶组件。

让我们先来看一个可能是史上最无聊的高阶组件:

function noId() {
  return function(Comp) {
    return class NoID extends Component {
      render() {
        const {id, ...others} = this.props;
        return (
          <Comp {...others}/>
        )
      }
    }
  }
}

const WithoutID = noId()(Comp);

这个例子向我们展示了高阶组件的工作方式:通过函数和闭包,改变已有组件的行为——这里是忽略id属性——而完全不需要修改任何代码。

之所以称之为高阶,是因为在React中,这种嵌套关系会反映到组件树上,层层嵌套就好像高阶函数的function in function一样,如图:

HOC-img

从图上也可以看出,组件树虽然嵌套了多层,但是实际渲染的DOM结构并没有改变。
如果你对这点有疑问,不妨自己写写例子试下,加深对React的理解。现在可以先记下结论:我们可以放心的使用多层高阶组件,甚至重复地调用,而不必担心影响输出的DOM结构。

借助函数的逻辑表现力,高阶组件的用途几乎是无穷无尽的:

适配器

有的时候你需要替换一些已有组件,而新组件接收的参数和原组件并不完全一致。

你可以修改所有使用旧组件的代码来保证传入正确的参数——考虑改行吧如果你真这么想

也可以把新组件做一层封装:

class ListAdapter extends Component {
    mapProps(props) {
        return {/* new props */}
    }
    render() {
        return <NewList {...mapProps(this.props)} />
    }
}

如果有十个组件需要适配呢?如果你不想照着上面写十遍,或许高阶组件可以给你答案

function mapProps(mapFn) {
    return function(Comp) {
        return class extends Component {
            render() {
                return <Comp {...mapFn(this.props)}/>
            }
        }
    } 
}

const ListAdapter = mapProps(mapPropsForNewList)(NewList);

借助高阶组件,关注点被分离得更加干净:只需要关注真正重要的部分——属性的mapping。

这个例子有些价值,却仍然不够打动人,如果你也这么想,请往下看:

处理副作用

纯组件易写易测,越多越好,这是常识。然而在实际项目中,往往有许多的状态和副作用需要处理,最常见的情况就是异步了。

假设我们需要异步加载一个用户列表,通常的代码可能是这样的:

class UserList extends Component {
  constructor(props) {
    super();
    this.state = {
      list: []
    }
  }
  componentDidMount() {
    loadUsers()
      .then(data=> 
        this.setState({list: data.userList})
      )
  }
  render() {
    return (
      <List list={this.state.list} />
    )
  }
  /* other bussiness logics */
}

实际情况中,以上代码往往还会和其它一些业务函数混杂在一起——我们创建了一个业务副作用混杂的、有状态的组件。

如果再来一个书单列表呢?再写一个BookList然后把loadUsers改成loadBooks ?
不仅代码重复,大量有状态和副作用的组件,也使得应用更加难以测试。

也许你会考虑使用Flux。它确实能让你的代码更清晰,但是在有些场景下使用Flux就像大炮打蚊子。比如一个异步的下拉选择框,如果要考虑复用的话,传统的Flux/Reflux几乎无法优雅的处理,Redux稍好一些,但仍然很难做优雅。关于flux/redux的缺点不深入,有兴趣的可以参考Cycle.js作者的文章

回到问题的本源:其实我们只想要一个能复用的异步下拉列表而已啊!

高阶函数试试?

import React, { Component } from 'react';

const DEFAULT_OPTIONS = {
  mapStateToProps: undefined,
  mapLoadingToProps: loading => ({ loading }),
  mapDataToProps: data => ({ data }),
  mapErrorToProps: error => ({ error }),
};
export function connectPromise(options) {
  return (Comp) => {
    const finalOptions = {
      ...DEFAULT_OPTIONS,
      ...options,
    };
    const {
      promiseLoader,
      mapLoadingToProps,
      mapStateToProps,
      mapDataToProps,
      mapErrorToProps,
    } = finalOptions;

    class AsyncComponent extends Component {
      constructor(props) {
        super(props);
        this.state = {
          loading: true,
          data: undefined,
          error: undefined,
        };
      }
      componentDidMount() {
        promiseLoader(this.props)
          .then(
            data => this.setState({ data, loading: false }),
            error => this.setState({ error, loading: false }),
          );
      }
      render() {
        const { data, error, loading } = this.state;

        const dataProps = data ? mapDataToProps(data) : undefined;
        const errorProps = error ? mapErrorToProps(error) : undefined;

        return (
          <Comp
            {...mapLoadingToProps(loading)}
            {...dataProps}
            {...errorProps}
            {...this.props}
          />
        );
      }
    }

    return AsyncComponent;
  };
}


const UserList = connectPromise({
    promiseLoader: loadUsers,
    mapDataToProps: result=> ({list: result.userList})
})(List); //List can be a pure component

const BookList = connectPromise({
    promiseLoader: loadBooks,
    mapDataToProps: result=> ({list: result.bookList})
})(List);

不仅大大减少了重复代码,还把散落各处的异步逻辑装进了可以单独管理和测试的笼子,在业务场景中,只需要纯组件 + 配置 就能实现相同的功能——而无论是纯组件还是配置,都是对单元测试友好的,至少比异步组件友好多了。

使用curry & compose

高阶组件的另一个亮点,就是对函数式编程的友好。你可能已经注意到,目前我写的所有高阶函数,都是形如:

config => {
    return Component=> {
        return HighOrderCompoent
    }
}

表示为config=> Component=> Component

写成嵌套的函数是为了手动curry化,而参数的顺序(为什么不是Component=> config=> Component),则是为了组合方便。关于curry与compose的使用,可以移步我的另一篇blog

举个栗子,前面讲了适配器和异步,我们可以很快就组合出两者的结合体:使用NewList的异步用户列表

UserList = compose(
  connectPromise({
    promiseLoader: loadUsers,
    mapResultToProps: result=> ({list: result.userList})
  }),
  mapProps(mapPropsForNewList)
)(NewList);

总结

在团队内部分享里,我的总结是三个词 Easy, Light-weight & Composable.

其实高阶组件并不是什么新东西,本质上就是Decorator模式在React的一种实现,但在相当一段时间内,这个优秀的模式都被人忽略。在我看来,大部分使用mixinclass extends的地方,高阶组件都是更好的方案——毕竟组合优于继承,而mixin——个人觉得没资格参与讨论。

使用高阶组件还有两个好处:

  1. 适用范围广,它不需要es6或者其它需要编译的特性,有函数的地方,就有HOC。

  2. Debug友好,它能够被React组件树显示,所以可以很清楚地知道有多少层,每层做了什么。相比之下无论是mixin还是继承,都显得非常隐晦。

值得庆幸的是,社区也明显注意到了高阶组件的价值,无论是大家非常熟悉的react-reduxconnect函数,还是redux-form,高阶组件的应用开始随处可见。

下次当你想写mixinclass extends的时候,不妨也考虑下高阶组件。

查看原文

阿健666 收藏了文章 · 2019-08-26

react-router v4 使用 history 控制路由跳转

原文地址: https://github.com/brickspert... (如果你觉得对你有帮助,可以去github上点个star哦。)

github上会更新,这里不更新

问题

当我们使用react-router v3的时候,我们想跳转路径,我们一般这样处理

  1. 我们从react-router导出browserHistory

  2. 我们使用browserHistory.push()等等方法操作路由跳转。

类似下面这样

import browserHistory from 'react-router';

export function addProduct(props) {
  return dispatch =>
    axios.post(`xxx`, props, config)
      .then(response => {
        browserHistory.push('/cart'); //这里
      });
}

but!! 问题来了,在react-router v4中,不提供browserHistory等的导出~~

那怎么办?我如何控制路由跳转呢???

解决方法

1. 使用 withRouter

withRouter高阶组件,提供了history让你使用~

import React from "react";
import {withRouter} from "react-router-dom";

class MyComponent extends React.Component {
  ...
  myFunction() {
    this.props.history.push("/some/Path");
  }
  ...
}
export default withRouter(MyComponent);

这是官方推荐做法哦。但是这种方法用起来有点难受,比如我们想在redux里面使用路由的时候,我们只能在组件把history传递过去。。

就像问题章节的代码那种场景使用,我们就必须从组件中传一个history参数过去。。。

2. 使用 Context

react-router v4Router 组件中通过Contex暴露了一个router对象~

在子组件中使用Context,我们可以获得router对象,如下面例子~

import React from "react";
import PropTypes from "prop-types";

class MyComponent extends React.Component {
  static contextTypes = {
    router: PropTypes.object
  }
  constructor(props, context) {
     super(props, context);
  }
  ...
  myFunction() {
    this.context.router.history.push("/some/Path");
  }
  ...
}

当然,这种方法慎用~尽量不用。因为react不推荐使用contex哦。在未来版本中有可能被抛弃哦。

3. hack

其实分析问题所在,就是v3中把我们传递给Router组件的history又暴露出来,让我们调用了~~

react-router v4 的组件BrowserRouter自己创建了history
并且不暴露出来,不让我们引用了。尴尬~

我们可以不使用推荐的BrowserRouter,依旧使用Router组件。我们自己创建history,其他地方调用自己创建的history。看代码~

  1. 我们自己创建一个history

// src/history.js

import createHistory from 'history/createBrowserHistory';

export default createHistory();
  1. 我们使用Router组件

// src/index.js

import { Router, Link, Route } from 'react-router-dom';
import history from './history';

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      ...
    </Router>
  </Provider>,
  document.getElementById('root'),
);
  1. 其他地方我们就可以这样用了

import history from './history';

export function addProduct(props) {
  return dispatch =>
    axios.post(`xxx`, props, config)
      .then(response => {
        history.push('/cart'); //这里
      });
}

4. 我非要用BrowserRouter

确实,react-router v4推荐使用BrowserRouter组件,而在第三个解决方案中,我们抛弃了这个组件,又回退使用了Router组件。

怎么办。 你去看看BrowserRouter源码,我觉得你就豁然开朗了。

源码非常简单,没什么东西。我们完全自己写一个BrowserRouter组件,然后替换第三种解决方法中的Router组件。嘿嘿。

讲到这里也结束了,我自己目前在使用第三种方法,虽然官方推荐第一种,我觉得用着比较麻烦唉。~

查看原文

阿健666 收藏了文章 · 2019-08-19

iphonex适配

第一步:设置网页在可视窗口的布局方式
ios11新增 viweport-fit 属性,使得页面内容完全覆盖整个窗口:

<meta name="viewport" content="width=device-width, viewport-fit=cover">

第二步:页面主体内容限定在安全区域内
env() 和 constant()ios11新增特性

● safe-area-inset-left:安全区域距离左边边界距离
● safe-area-inset-right:安全区域距离右边边界距离
● safe-area-inset-top:安全区域距离顶部边界距离
● safe-area-inset-bottom:安全区域距离底部边界距离

这里我们只需要关注 safe-area-inset-bottom 这个变量,因为它对应的就是小黑条的高度(横竖屏时值不一样)。

body {
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
}

第三步:fixed 元素的适配
● 类型一:fixed 完全吸底元素(bottom = 0)

{
    padding-bottom: constant(safe-area-inset-bottom);
    padding-bottom: env(safe-area-inset-bottom);
}

● 类型二:fixed 非完全吸底元素(bottom ≠ 0),比如 “返回顶部”、“侧边广告” 等

{
    margin-bottom: constant(safe-area-inset-bottom);
    margin-bottom: env(safe-area-inset-bottom);
}

第四步:如果我们只希望 iPhoneX 才需要新增适配样式,我们可以配合 @supports 来隔离兼容样式

@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
    div {
        margin-bottom: constant(safe-area-inset-bottom);
        margin-bottom: env(safe-area-inset-bottom);
    }
}
查看原文

阿健666 收藏了文章 · 2019-08-04

React-Redux 中文文档

介绍

快速开始

React-ReduxRedux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据

安装

在你的React app中使用React-Redux:

npm install --save react-redux

或者

yarn add react-redux

Providerconnect

React-Redux 提供<Provider/>组件,能够使你的整个app访问到Redux store中的数据:

import React from "react";
import ReactDOM from "react-dom";

import { Provider } from "react-redux";
import store from "./store";

import App from "./App";

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

React-Redux提供一个connect方法能够让你把组件和store连接起来。

通常你可以以下面这种方式调用connect方法:

import { connect } from "react-redux";
import { increment, decrement, reset } from "./actionCreators";

// const Counter = ...

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    counter: state.counter
  };
};

const mapDispatchToProps = { increment, decrement, reset };

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

基础教程

为了进一步了解如何实际使用React-Redux,我们将一步一步创建一个todo listapp

一个Todo List实例

跳转到:

React UI 组件

我们所用到的React UI组件如下:

  • TodoApp:我们应用的入口组件,它renderAddTodo,TodoListVisibilityFilters组件
  • AddTodo:允许用户在点击Add Todo按钮后,向todo list中加入一个新的待办项:

    • 使用一个受控input监听onChange事件以设置state
    • 当用户单击Add Todo按钮后,该组件dispatch一个action,向store中添加一个新的待办项。(这个action是我们由React-Redux提供的)
  • TodoList:渲染出待办项列表的组件:

    • 当一个VisibilityFilter被选择后,能够渲染出所匹配的待办项列表
  • Todo:仅负责渲染单个todo待办项:

    • 渲染出待办项的内容,通过横贯线表示该项已被完成
    • 触发onClick事件后,dispatch一个能切换完成状态的action
  • VisibilityFilters:渲染一个filters集合:_all_,_complete_ 以及 _incomplete_。单击每一项能够筛选匹配的todos:

    • 从父组件接收一个activeFilter属性以表示当前用户选择的过滤条件。选中的filter会显示出下划线。
    • 能够dispatch名为setFilteraction以更新已选过滤条件
  • constants:保存我们的app所有需要的常量数据
  • 最后,index将app渲染到DOM中

the Redux Store

应用的Redux部分遵循Redux官方文档建议模式进行搭建:

  • Store:

    • todos:标准化的todos的reducer。包含了byIds的待办项map对象结构,和一个包含了所有待办项id的allIds数组
    • visibilityFilters:简单的字符串all, completed, or incomplete.
  • Action Creators:

    • addTodo:创建增添待办项的action。接收一个string变量content,返回ADD_TODO类型的action,以及一个payload对象(包含了自增的idcontent属性)
    • toggleTodo:创建一个切换待办项的action。只接收一个number类型的变量id,返回TOGGLE_TODO类型action以及仅含id属性的payload对象。
    • setFilter:创建设置app当前过滤条件的action。接收一个string类型变量filter返回一个SET_FILTER类型action一集一个包含filter自身的payload对象。
  • Reducers

    • todos reducer:

      
      - 在接收到`ADD_TODO` action时,将`id`追加到`allIds`数组,并且更新`byIds`
      - 在接收到`TOGGLE_TODO` action时,切换`completed`状态
      
    • VisibilityFilters reducer:在接收到SET_FILTERaction 时负责更新VISIBILITY_FILTERS状态
  • Action Types

    • 使用一个actionTypes.js文件来保存所有的action types常量,以便复用
  • Selectores

    • getTodoList:从todos store中返回allIds列表
    • getTodoById:通过id查询store中的todo项
    • getTodos:有点复杂。它接收allIds数组中的所有id,找到每一个对应的byIds中的todo,返回最终的todos数组
    • getTodosByVisibilityFilter:根据筛选条件过滤todos

你可以查看上面这些UI组件的和尚未连接的Redux Store源码

下面我们将展示如何使用React-Reduxstore连接到我们的app中

创建Store

首先我们需要让store成为我们app中可访问的对象。为此,我们将用React-Redux提供给我们的<Provider/>组件包裹我们的根组件

// index.js
import React from "react";
import ReactDOM from "react-dom";
import TodoApp from "./TodoApp";

import { Provider } from "react-redux";
import store from "./redux/store";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <TodoApp />
  </Provider>,
  rootElement
);

要注意到store是以一个prop<Provider/>传递到被包裹的<TodoApp/>中的

连接组件

React-Redux提供一个connect方法使你可以从Redux store中读取数据(以及当store更新后,重新读取数据)

connect方法接收两个参数,都是可选参数:

  • mapStateToProps:每当store state发生变化时,就被调用。接收整个store state,并且返回一个该组件所需要的数据对象
  • mapDispatchToProps:这个参数可以是一个函数对象

    • 如果是一个函数,一旦该组件被创建,就会被调用。接收dispatch作为一个参数,并且返回一个能够使用dispatch来分发actions的若干函数组成的对象
    • 如果是一个action creators构成的对象,每一个action creator将会转化为一个prop function并会在调用时自动分发actions。注意: 我们建议使用这种形式。

通常,你可以这样去connect

const mapStateToProps = (state, ownProps) => ({
  // ... 从state中处理的一些数据,以及可选的ownProps
});

const mapDispatchToProps = {
  // ... 通常是action creators构成的对象
};

// `connect`返回一个新的函数,可以接收一个待包装的组件
const connectToStore = connect(
  mapStateToProps,
  mapDispatchToProps
);
// 上面的函数能够返回一个已经包装、连接过的组件
const ConnectedComponent = connectToStore(Component);

// 我们通常写成一条语句如下:
connect(
  mapStateToProps,
  mapDispatchToProps
)(Component);

下面让我们开始编写<AddTodo/>。它要能够触发store的变化从而增加新的todos。因此,他要能够向store dispatch actions。下面就是具体流程。

我们的addTodo action创建函数如下所示:

// redux/actions.js
import { ADD_TODO } from "./actionTypes";

let nextTodoId = 0;
export const addTodo = content => ({
  type: ADD_TODO,
  payload: {
    id: ++nextTodoId,
    content
  }
});

// ... other actions

把它传递到connect,我们的组件就能够以一个prop接收到它。并且一旦我们调用,它就能够自动的分发actions

// components/AddTodo.js

// ... other imports
import { connect } from "react-redux";
import { addTodo } from "../redux/actions";

class AddTodo extends React.Component {
  // ... component implementation
}

export default connect(
  null,
  { addTodo }
)(AddTodo);

注意到现在<AddTodo/>已经被一个父组件<Connect(AddTodo)/>所包装。同时,<AddTodo/>现在拥有了一个propaddTodo action

我们也需要实现handleAddTodo方法以便分发addTodo action 并且重置input

// components/AddTodo.js

import React from "react";
import { connect } from "react-redux";
import { addTodo } from "../redux/actions";

class AddTodo extends React.Component {
  // ...

  handleAddTodo = () => {
    // 分发action以增加todo项
    this.props.addTodo(this.state.input);

    // 将state的input置为空字符串
    this.setState({ input: "" });
  };

  render() {
    return (
      <div>
        <input
          onChange={e => this.updateInput(e.target.value)}
          value={this.state.input}
        />
        <button className="add-todo" onClick={this.handleAddTodo}>
          Add Todo
        </button>
      </div>
    );
  }
}

export default connect(
  null,
  { addTodo }
)(AddTodo);

现在我们的<AddTodo/>已经连接到了store。当我们增加一个新的todo时,该组件就能够分发action从而改变store。我们现在还不能看到这个效果,因为别的组件尚未连接到store。如果你安装了Redux DevTools谷歌浏览器扩展程序,那么你可以看到action已经被分发了:

你也能够看到store相应地发生了变化。

<TodoList/>组件负责渲染todos列表。因此,他需要从store中读取数据。我们通过调用connect方法,并向其中传入mapStateToProps参数从而提供给组件所需要的部分来自store数据。

我们的<Todo/>组件接收一个todo项作为prop。我们从todosbtIds对象获取到所需信息。然而,我们也需要store中的allIds字段的信息,以便指明哪些todos以哪种顺序渲染。我们的mapStateToProps方法可能长这个样子:

// components/TodoList.js

// ...other imports
import { connect } from "react-redux";

const TodoList = // ... UI component implementation

const mapStateToProps = state => {
  const { byIds, allIds } = state.todos || {};
  const todos =
    allIds && allIds.length
      ? allIds.map(id => (byIds ? { ...byIds[id], id } : null))
      : null;
  return { todos };
};

export default connect(mapStateToProps)(TodoList);

幸好我们有一个selector专门做这个事情,我们只需要简单地导入selector并且使用它。

// redux/selectors.js

export const getTodosState = store => store.todos;

export const getTodoList = store =>
  getTodosState(store) ? getTodosState(store).allIds : [];

export const getTodoById = (store, id) =>
  getTodosState(store) ? { ...getTodosState(store).byIds[id], id } : {};

export const getTodos = store =>
  getTodoList(store).map(id => getTodoById(store, id));
// components/TodoList.js

// ...other imports
import { connect } from "react-redux";
import { getTodos } from "../redux/selectors";

const TodoList = // ... UI component implementation

export default connect(state => ({ todos: getTodos(state) }))(TodoList);

我们建议将所有复杂的查找和计算数据的方法封装到selector中。此外,你今后可以通过使用Reselect编写“memoized” selectors来跳过不必要的工作从而优化性能。(查阅Redux中文文档 | 计算衍生数据以及博客:Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance以获得更多的关于为何以及如何使用selector方法的信息)

既然我们的<TodoList/>也已经连接到了store。它应该能够接收到todos列表了,遍历他们,然后把每一个传递给<Todo/>组件。<Todo/>组件会将它们渲染到浏览器中,现在尝试增加一个todo待办项。它应该能立即出现在我们的todo列表中!

https://i.imgur.com/N68xvrG.png

我们接下来会connect更多的组件。在开始之前,让我们先暂停一下,首先学习更多关于connect的知识。

常见的调用connect的方式

有不同的方式来调用connect,这取决于你现在编写的组件类型。现对常用的方式进行总结:

 不订阅Store订阅Store
不注入Action Creatorsconnect()(Component)connect(mapStateToProps)(Component)
注入Action Creatorsconnect(null, mapDispatchToProps)(Component)connect(mapStateToProps, mapDispatchToProps)(Component)

不订阅store并且不注入action创建函数

如果你调用connect方法并且不传入任何参数,那么你的组件将会:

  • store改变时不能够重新渲染
  • 接收一个props.dispatch方法以便你手动分发actions
// ... Component
export default connect()(Component); // 组件将接收 `dispatch` (正如 <TodoList />!)

订阅store但不注入action创建函数

如果你调用connect方法并且只传入了mapStateToProps方法,你的组件将会:

  • 订阅mapStateToProps从store中提取的部分值,当这些值改变时会重新渲染
  • 接收一个props.dispatch以便你手动分发actions
// ... Component
const mapStateToProps = state => state.partOfState;
export default connect(mapStateToProps)(Component);

不订阅store但注入action创建函数

如果你调用connect方法并只传入mapDispatchToProps参数,你的组件将会:

  • store改变时不重新渲染
  • props形式接收每个你通过mapDispatchToProps注入的action创建函数,能够在你调用后自动分发actions
import { addTodo } from "./actionCreators";
// ... Component
export default connect(
  null,
  { addTodo }
)(Component);

订阅store并且注入action创建函数

如果你在connect方法中传入了mapStateToPropsmapDispatchToProps,你的组件将会:

  • 订阅mapStateToProps从store中提取的部分值,当这些值改变时会重新渲染
  • props形式接收每个你通过mapDispatchToProps注入的action创建函数,能够在你调用后自动分发actions
import * as actionCreators from "./actionCreators";
// ... Component
const mapStateToProps = state => state.partOfState;
export default connect(
  mapStateToProps,
  actionCreators
)(Component);

上面这四个例子基本覆盖了所有connect的用法。如果想了解更多关于connnect的信息,可以继续阅读[API 部分]()内容


现在我们把<TodoApp/>余下的部分连接到store

我们该如何实现切换todos的操作呢?一个聪明的读者也许已经有答案了。如果你截止目前的所有步骤都是紧跟指导完成的,现在是一个抛开指南、自己实现该功能的绝佳时机。不出所料,我们以类似的方法连接<Todo/>以分发toggleTodo

// components/Todo.js

// ... other imports
import { connect } from "react-redux";
import { toggleTodo } from "../redux/actions";

const Todo = // ... 实现组件

export default connect(
  null,
  { toggleTodo }
)(Todo);

现在我们的todo能够切换为complete状态了。马上就好了!

https://i.imgur.com/4UBXYtj.png

终于,让我们开始实现VisibilityFilters功能。

<VisiblityFilterse/>组件需要从store中读取当前选中的过滤条件,并且分发actions。因此,我们需要把mapStateToProps以及mapDispatchToProps都传递给connect方法。mapStateToProps能够作为visiblityFilter状态的一个简单的访问器。mapDispatchToProps会包括setFilteraction创建函数。

// components/VisibilityFilters.js

// ... other imports
import { connect } from "react-redux";
import { setFilter } from "../redux/actions";

const VisibilityFilters = // ... 组件实现

const mapStateToProps = state => {
  return { activeFilter: state.visibilityFilter };
};
export default connect(
  mapStateToProps,
  { setFilter }
)(VisibilityFilters);

同时,我们也要更新我们的<TodoList/>组件来根据筛选条件过滤todos。先前我们传递给<TodoList/> connect 方法的mapStateToProps正如一个简单的选择了所有列表中的todos的selector。现在我们来写一个selector以通过todos的状态来进行筛选。

// redux/selectors.js

// ... other selectors
export const getTodosByVisibilityFilter = (store, visibilityFilter) => {
  const allTodos = getTodos(store);
  switch (visibilityFilter) {
    case VISIBILITY_FILTERS.COMPLETED:
      return allTodos.filter(todo => todo.completed);
    case VISIBILITY_FILTERS.INCOMPLETE:
      return allTodos.filter(todo => !todo.completed);
    case VISIBILITY_FILTERS.ALL:
    default:
      return allTodos;
  }
};

然后借助selector连接到store

// components/TodoList.js

// ...

const mapStateToProps = state => {
  const { visibilityFilter } = state;
  const todos = getTodosByVisibilityFilter(state, visibilityFilter);
  return { todos };
};

export default connect(mapStateToProps)(TodoList);

现在我们已经用React-Redux完成了一个很简单的todo app案例。我们所有的组件都已经连接到了store。是不是很棒呢?🎉🎊

https://i.imgur.com/ONqer2R.png

链接

获取更多帮助

使用 React-Redux

Connect:使用mapStateToProps抽取数据

作为传递给connect的第一个参数,mapStateToProps用来从store中选择被连接的组件所需要的部分数据。常以mapState缩写来表示。

  • 每当store state改变时就被调用
  • 接收整个store state,并且返回一个组件需要的数据对象

声明mapStateToProps

mapStateToProps应该声明为一个方法:

function mapStateToProps(state, ownProps?)

他接收的第一个参数是state,可选的第二个参数时ownProps,然后返回一个被连接组件所需要的数据的纯对象。

这个方法应作为第一个参数传递给connect,并且会在每次Redux store state改变时被调用。如果你不希望订阅store,那么请传递null或者undefined替代mapStateToProps作为connect的第一个参数。

无论mapStateToProps是使用function关键字声明的(function mapState(state) { } ) 还是以一个箭头函数(const mapState = (state) => { } ) 的形式定义的——它都能够以同样的方式生效。

参数

  1. state
  2. ownProps(可选)

state

mapStateToProps的第一个参数是整个Redux store state对象(与store.getState()方法返回的值相同)。因此第一个参数通常命名为state(当然你也可以选择别的名字,但是叫store就不推荐了——因为它是state值而不是store实例)

mapStateToProps方法至少要传递state参数。

// TodoList.js 

function mapStateToProps(state) {
  const { todos } = state;
  return { todoList: todos.allIds };
};
    
export default connect(mapStateToProps)(TodoList);

ownProps(可选)

如果你的组件需要用自身的props数据以从store中检索出数据,你可以传入第二个参数,ownProps。这个参数将包含所有传递给由connect生成的包装组件的props

// Todo.js

function mapStateToProps(state, ownProps) {
  const { visibilityFilter } = state;
  const { id } = ownProps;
  const todo = getTodoById(state, id);

  // 组件额外接收:
  return { todo, visibilityFilter };
};

// 之后,在你的应用里,渲染一个如下父组件:
<ConnectedTodo id={123} />
// 你的组件接收 props.id, props.todo, 以及 props.visibilityFilter

你不需要把ownProps中的值再添加入mapStateToProps返回的对象中,connect会自动的把这些不同源的prop合并为一个最终的prop集。

返回值

你的mapStateToProps方法应该返回一个包含了组件用到的数据的纯对象:

  • 每一个对象中的字段都将作为你组件的一个prop
  • 字段中的值用来判断你的组件是否需要重新渲染

例如:

function mapStateToProps(state) {
  return {
    a : 42,
    todos : state.todos,
    filter : state.visibilityFilter
  }
}

// 组件会接收: props.a, props.todos,以及 props.filter
注意:在一些高级场景中,你可能需要更多地对于渲染性能的控制,mapStateToProps也可以返回一个方法。在这种情况下,那个所返回的方法会做为一个特定组件实例的最终的mapStateToProps。这样一来,你就可以对每个实例进行memoization。参考[高级用法]()部分以获取更多信息。也可以看PR #279以及上面增加的测试。但大部分应用根本不需要这样做

使用指南

mapStateToProps改造从store中取出的数据

mapStateToProps方法能够,且应该,做更多的事情,而不仅仅是返回一个state.someSlice他们有责任去改造组建所需要的store中的数据。比如说,返回一个特定prop名称的值,从state树中不同部分取出数据片段并合并为一个整体,以及以不同的方式转化store。

使用Selector方法去抽取和转化数据

我们强烈建议使用selector方法去封装抽取state树中的特定位置的值。Memoized selector方法也在提高应用性能上起到了关键作用。(参考本页以下部分:[高级用法:性能]()以获取更多关于为何以及如何使用selectors的细节)

mapStateToProps方法应该足够快

一旦store改变了,所有被连接组件中的所有mapStateToProps方法都会运行。因此,你的mapStateToProps方法一定要足够快。这也意味着缓慢的mapStateToProps方法会成为你应用的一个潜在瓶颈。

作为“重塑数据”想法的一部分,mapStateToProps方法经常需要以各种方式来转化数据(比如过滤数组,遍历IDs数组映射到对应的对象,或者从Immutable.js对象中抽取纯js值)。这些转化的开销通常很高昂,不仅表现在运行转化操作的开销上,也表现在判断最终是否要更新组件上。如果的确需要考虑性能问题了,那么要确保你的这些转化只发生在所输入的值发生变化的时候。

mapStateToProps方法应该纯净且同步

正如Redux Reducer,一个mapStateToProps方法应该是100%纯净的并且是同步的。他应该仅接受state(以及ownProps)作为参数,然后以props形式返回组件所需要的数据。他应该触发任何异步操作,如AJAX请求数据,方法也不能声明为async形式。

mapStateToProps和性能

返回值决定你的组件是否需要更新

React-Redux 内部实现了shouldComponentUpdate方法以便在组件用到的数据发生变化后能够精确地重新渲染。默认地,React-Redux使用“===”对mapStateToProps返回的对象的每一个字段逐一对比,以判断内容是否发生了改变。但凡有一个字段改变了,你的组件就会被重新渲染以便接收更新过的props值。注意到,返回一个相同引用的突变对象(mutated object)是一个常见错误,因为这会导致你的组件不能如期重新渲染。

总结一下传入mapStateToProps参数来抽取store中的数据的connect方法包装过的组件行为:

<span/>state=>stateProps(state,ownProps)=>stateProps
mapStateToProps运行条件:store state 发生改变store state发生改变

任何ownProps字段改变
组件重新渲染条件任何stateProps字段改变任何stateProps字段改变

任何ownProps字段改变

仅在需要时返回新的对象引用

React-Redux进行浅比较来检查mapStateToProps的结果是否改变了。返回一个新对象或数组引用十分容易操作,但会造成你的组件在数据没变的情况下也重新渲染。

很多常见的操作都会返回新对象或数组引用:

  • 创建新的数组:使用someArray.map()或者someArray.filter()
  • 合并数组:array.concat
  • 截取数组:array.slice
  • 复制值:Object.assgin
  • 使用扩展运算符:{...oldState,...newData}

把这些操作放在memeoized selector functions中确保它们只在输入值变化后运行。这样也能够确保如果输入值没有改变,mapStateToProps仍然返回与之前的相同值,然后connect就能够跳过重渲染过程。

仅在数据改变时运行开销大的操作

转化数据经常开销很大(并且通常会创建一个新的对象引用)。为了使你的mapStateToProps方法足够快,你应该相关数据改变时重新运行这些复杂的转化。

有下面几种形式来达到这样的目的:

  • 一些转化可以在action创建函数或者reducer中运算,然后可以把转化过的数据储存在store中
  • 转换也可以在组件的render()生命周期中完成
  • 如果转化必须要在mapStateToProps方法中完成,那么我们建议使用memoized selector functions以确保转化仅发生在输入的数据变化时。

考虑Immutable.js性能

Immutable.js 的作者Lee Byron在Twitter中明确建议了如果开始考虑性能了要避免使用toJS

Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless.

还有一些别的关于Immutable.js的性能提升建议——参见下方的链接列表。

行为及总结

mapStateToProps在store state相同的情况下不会运行

connect生成的包装组件会订阅Redux store。每当action被分发后,它就调用store.getState()并检查是否lastState===currentState。如果两个状态值引用完全相同,那么mapStateToProps就不会运行,因为组件假设了你余下的store state也没有发生改变。

Redux的combineReducers功能函数会尝试对其优化。如果所有reducer都没有返回新值,那么combineReducers会返回旧state对象而不是新的。这意味着,一个reducer中的突变不会使根state对象更新,当然UI也不会重新渲染。

声明参数的数量影响行为

当仅有(state)时,每当根store state对象不同了,函数就会运行。

当有(state,ownProps)两个参数时,每当store state不同、每当包装props变化时,函数都会运行。

这意味着你不应该增加ownProps参数,除非你实在是需要它,否则你的mapStateToProps函数会比它实际需要运行次数运行更多次。

关于这个行为有一些极端案例。arguments的数量决定了mapStateToProps是否接收ownProps参数

如果先前定义函数的时候包含了一个命令参数,mapStateToProps就不会接收ownProps

function mapStateToProps(state) {
  console.log(state);        // state
  console.log(arguments[1]); // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
  console.log(state);    // state
  console.log(ownProps); // undefined
}

如果之前定义的函数包含了0个或2个命令参数,他就需要接收ownProps参数:

function mapStateToProps(state, ownProps) {
  console.log(state);    // state
  console.log(ownProps); // ownProps
}

function mapStateToProps() {
  console.log(arguments[0]); // state
  console.log(arguments[1]); // ownProps
}

function mapStateToProps(...args) {
  console.log(args[0]); // state
  console.log(args[1]); // ownProps
}

链接和参考

教程

性能

Q&A

Connect: 使用mapDispatchToProps分发actions

作为第二个传入connect的参数,mapDispatchToProps可以实现向store中分发acions。

dispatch是Redux store实例的一个方法,你会通过store.dispatch来分发一个action。这也是唯一触发一个state变化的途径

使用React-Redux后,你的组件就不再需要直接和store打交道了——connect会为你完成这件任务,React-Redux提供了两种可以分发actions的方式:

  • 默认地,一个已连接组件可以接收props.dispatch然后自己分发actions
  • connect能够接收一个mapDispatchToProps作为第二个参数,这将让你能够创建dispatch调用方法,然后把这些方法作为props传递给你的组件。

分发(Dispatching)的途径

默认:作为一个prop的dispatch

如果你不为connect()指明第二个参数,你的组件会默认接收dispatch。比如:

connect()(MyComponent);
// 与下面语句等价
connect(
  null,
  null
)(MyComponent);

// 或者
connect(mapStateToProps /** 没有第二个参数 */)(MyComponent);

一旦你以这种方式连接了你的组件,你的组件就会接收props.dispatch。你可以用它来向store中分发actions。

function Counter({ count, dispatch }) {
  return (
    <div>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
      <button onClick={() => dispatch({ type: "RESET" })}>reset</button>
    </div>
  );
}

提供一个mapDispatchToProps参数

提供一个mapDispatchToProps参数能够让你指明你的组件所实际需要分发的那些actions。它允许你提供action分发函数作为props,这样一来,你不用再进行props.dispatch(() => increment())调用,你可以直接props.increment()。你这么做是出于下面几个原因:

更加声明式的

首先,把分发逻辑封装到函数中使得整个实现更加声明式。分发一个action然后让Redux store处理数据流,表现出了你如何实现这一行为而不仅仅是只关心它做了什么。

单击按钮后分发一个action也许是个不错的例子。把一个button直接连接从概念上来讲有点说不通,button也没有dispatch引用。

// button需要有意识地 "dispatch"
<button onClick={() => dispatch({ type: "SOMETHING" })} />

// button看起来不像在 "dispatch",
<button onClick={doSomething} />

一旦你把所有的action creators使用函数封装起来之后,你的组件就不需要再dispatch了。因此,如果你定义了mapDispatchToProps被连接组件就不再接收到dispatch

把action分发逻辑向子(未连接)组件传递

此外,你现在也能够向下传递你的action分发函数给子组件(可能尚未连接)。这样就能够使更多的组件分发actions,且它们对Redux也是无感知的。

// 把toggleTodo 传递给子组件
// 让Todo 能分发 toggleTodo action
const TodoList = ({ todos, toggleTodo }) => (
  <div>
    {todos.map(todo => (
      <Todo todo={todo} onClick={toggleTodo} />
    ))}
  </div>
);

这就是React-Redux的connect所做的工作——封装与Redux Store对话的逻辑,并且你不再需要操心。你也应该在你的实现中充分利用这一点。

两种mapDispatchToProps的形式

mapDispatchToProps参数有两种形式:函数形式自定义化程度更高,对象形式更简单。

  • 函数形式:更高自由度、能够访问dispatch和可选择的ownProps
  • 对象形式:更声明式,更易于使用
注意:我们建议使用对象形式的mapDispatchToProps,除非你需要以某种自定义形式进行分发操作

mapDispatchToProps定义为一个函数

mapDispatchToProps定义为一个函数使你更灵活地定义你的组件能够接收到的函数、以及这些函数如何分发actions。你对dispatchownProps都具有访问权限。你可以借此机会编写你的连接组件的自定义函数。

参数

  1. dispatch
  2. ownProps(可选)

dispatch

mapDispatchToProps函数调用时以dispatch作为第一个参数。通常情况下,你会利用这个参数来返回一个内部调用了dispatch()的新函数,然后内部传递一个纯的action对象或者action创建函数的返回值。

const mapDispatchToProps = dispatch => {
  return {
    // 分发纯action对象
    increment: () => dispatch({ type: "INCREMENT" }),
    decrement: () => dispatch({ type: "DECREMENT" }),
    reset: () => dispatch({ type: "RESET" })
  };
};

你也可能需要把一些参数转发给你的action创建函数

const mapDispatchToProps = dispatch => {
  return {
    // 直接转发参数
    onClick: event => dispatch(trackClick(event)),

    // 间接转发参数
    onReceiveImpressions: (...impressions) =>
      dispatch(trackImpressions(impressions))
  };
};

ownProps(可选)

你的mapDispatchToProps函数是可以接收两个参数的,第一个是dispatch,传递给连接组件的props即为mapDispatchToProps的第二个参数,然后在组件接收到新的props后会重新调用。

这意味着,你应该在组件props改变阶段重新把新的props绑定到action分发函数中去,而不是在组件重新渲染阶段进行。

// 在组件re-rendering阶段绑定
<button onClick={() => this.props.toggleTodo(this.props.todoId)} />;

// 在 props 改变阶段绑定
const mapDispatchToProps = (dispatch, ownProps) => {
  toggleTodo: () => dispatch(toggleTodo(ownProps.todoId));
};

返回值

你的mapDispatchToProps函数应该的返回一个纯对象。

  • 每一个对象的字段都会作为你的组件的一个独立prop,并且字段的值通常是一个调用后能分发action的函数。
  • 如果你在dispatch()中使用了action创建函数(区别于纯对象形式的action),通常约定字段名与action创建函数的名称相同
const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const reset = () => ({ type: "RESET" });

const mapDispatchToProps = dispatch => {
  return {
    // 分发由action creators创建的actions
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    reset: () => dispatch(reset())
  };
};

mapDispatchToProps的函数返回值会合并到你的组件props中去。你就能够直接调用它们来分发action。

function Counter({ count, increment, decrement, reset }) {
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

(Counter案例的完整代码参见:CodeSandbox)

使用bindActionCreators定义mapDispatchToProps函数

手动封装这些函数实在是繁琐,所以Redux提供了一个函数简化这个操作。

bindActionCreators将值为action creators的对象,转化为同键名的新对象,但将每个action creators封装到一个dispatch调用中,以便可以直接调用它们。参阅Redux | bindActionCreators

bindActionCreators接收两个参数:

  1. 一个函数(action creator)或一个对象(每个属性为一个action creator)
  2. dispatch

bindActionCreators生成的包装函数会自动转发它们所有的参数,所以你不需要在手动操作了。

import { bindActionCreators } from "redux";

const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const reset = () => ({ type: "RESET" });

// 绑定一个action creator
// 返回 (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch);

// 绑定一个action creators构成的object
const boundActionCreators = bindActionCreators({ increment, decrement, reset }, dispatch);
// 返回值:
// {
//   increment: (...args) => dispatch(increment(...args)),
//   decrement: (...args) => dispatch(decrement(...args)),
//   reset: (...args) => dispatch(reset(...args)),
// }

mapDispatchToProps中使用bindActionCreators函数:

import { bindActionCreators } from "redux";
// ...

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ increment, decrement, reset }, dispatch);
}

// 组件能接收到 props.increment, props.decrement, props.reset
connect(
  null,
  mapDispatchToProps
)(Counter);

手动注入dispatch

如果提供了mapDispatchToProps,组件将不再接收到默认的dispatch。但你可以通过在mapDispatchToPropsreturn中添加dispatch把它重新注入你的组件。多数情况下,你不需要这么做。

import { bindActionCreators } from "redux";
// ...

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    ...bindActionCreators({ increment, decrement, reset }, dispatch)
  };
}

mapDispatchToProps定义为一个对象

你已经注意到了,在React组件中分发Redux actions的过程都十分类似:定义action创建函数,把它包装在形如(…args) => dispatch(actionCreator(…args))的另一个函数,然后把那个包装函数作为props 传递给你的组件。

因为这一流程实在是太通用了,connect支持了一个“对象简写”形式的mapDispatchToProps参数:如果你传递了一个由action creators构成的对象,而不是函数,connect会在内部自动为你调用bindActionCreators

我们建议适中使用这种“对象简写”形式的mapDispatchToProps,除非你有特殊理由需要自定义dispatching行为

注意到:

  • 每个mapDispatchToProps对象的字段都被假设为一个action创建函数
  • 你的组件不再接收dispatch作为一个prop
// React-Redux 自动为你做:
dispatch => bindActionCreators(mapDispatchToProps, dispatch);

因此,我们的mapDispatchToProps可以简写为:

const mapDispatchToProps = {
  increment,
  decrement,
  reset
};

既然变量名取决于你,你可能想把它命名为actionCreators或者甚至直接在调用connect时使用一个行内对象:

import {increment, decrement, reset} from "./counterActions";

const actionCreators = {
  increment,
  decrement,
  reset
}

export default connect(mapState, actionCreators)(Counter);

// 或者
export default connect(
  mapState,
  { increment, decrement, reset }
)(Counter);

常见问题

为什么组件不再接收dispatch?

也就是说会报错:

TypeError: this.props.dispatch is not a function

在你试图调用this.props.dispatch时这个错误就很常见了,因为实际上dispatch并没有注入到你的组件。

dispatch仅在下面这些情况下注入组件:

  1. 你没有提供mapDispatchToProps

默认的mapDispatchToProps只是简单的dispatch => ({ dispatch })。如果你不提供mapDispatchToPropsdispatch会以上面的形式自动提供给你。

换言之,也就是你这么做了:

//组件接收 `dispatch`
connect(mapStateToProps /** 没有第二参数*/)(Component);
  1. 你自定义的mapDispatchToProps明确地返回了dispatch

你也许想把dispatch带回你的组件,通过形如下面的定义方法:

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    reset: () => dispatch(reset()),
    dispatch
  };
};

或者,使用bindActionCreators

import { bindActionCreators } from "redux";

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    ...bindActionCreators({ increment, decrement, reset }, dispatch)
  };
}

本错误可参考:Redux’s GitHub issue #255.

有关是否需要对组件提供dispatch的讨论(Dan Abramov对#255的回复)。您可以阅读它们以进一步了解目前这么做的目的。

我能不能不使用mapStateToProps而仅使用mapDispatchToProps?

当然。你可以通过给第一个参数传入nullundefined来跳过它。你的组件就不会订阅store但仍然能够接收到mapDispatchToProps定义的dispatch props

connect(
  null,
  mapDispatchToProps
)(MyComponent);

我可以调用store.dispatch吗?

无论是直接import的还是从context拿到的store,这都不是一种与store交互的良好模式(参见Redux FAQ entry on store setup以获取更多详情)。让React-Redux的connect来获取对store的访问权,并且使用dispatch分发actions。

链接和参考

教程

相关文档

Q&A

API 参考

API

Provider

使Redux store可用于connect()调用下面组件层次结构中。通常,如果没有<Provider>包装父级或祖先组件,则无法使用connect()

如果你 实在 是需要,你可以手动地将store作为一个prop传递给被连接组件,但是我们仅建议在单元测试中对store伪造(stub),或者在非完全React代码库中这么做。通常,你就使用<Provider>吧。

props

  • storeRedux Store):你应用里的唯一Redux store
  • children(ReactElement):根组件

实例

Vanilla React

ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

React Router

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <Route path="foo" component={Foo}/>
        <Route path="bar" component={Bar}/>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

将一个React组件连接到Redux Store。connectadvancedConnect的对外提供的一个较为常用的API,能够用于大多数场景。

它不会改变传递给它的组件类,而是返回了一个新的、被连接的组件供你使用。

参数

  • [mapStateToProps(state, [ownProps]): stateProps] (Function):

如果指定过了这个参数,那么新组件就会向Redux store订阅更新。这意味着任何时候store一旦更新,mapStateToProps就会被调用。mapStateToProps的结果必须是一个纯对象,之后该对象会合并到组件的props

如果你的mapStateToProps函数被声明为接收两个参数,那么第一个参数就是store state,第二个参数就是传递给连接组件的propsmapStateToProps函数也会在连接组件的props通过浅比较发现改变时重新运行。(第二个参数约定命名为ownProps)。

注意:在一些高级场景中,你可能需要更多地对于渲染性能的控制,mapStateToProps也可以返回一个方法。在这种情况下,那个所返回的方法会做为一个特定组件实例的最终的mapStateToProps。这样一来,你就可以对每个实例进行memoization。参考[高级用法]()部分以获取更多信息。也可以看PR #279以及上面增加的测试。但大部分应用根本不需要这样做

mapStateToPros函数的第一个参数是整个Redux Store state,然后该函数返回一个可以传递给连接组件的props对象,这个常被称为 selector 。使用reselect来高效地构建selectors以及计算衍生数据

  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

如果传递的是一个对象,其内部的每一个函数都被假定为一个Redux action 创建函数。这个对象会作为props传递给连接组件,且其内部每个字段名都与action creators相同,但被一个能调用dispatch方法的函数包装,这样一来这些分发函数就可以直接调用。

如果传递的是一个函数,其第一个参数为dispatch。你想如何使用dispatch去绑定action创建函数都可以。(贴士:你可以使用Redux提供的bindActionCreators()方法)

如果你的mapDispatchToProps方法被声明为接收两个变量,那么其第一个参数是dispatch,第二个参数就是传递给连接组件的propsmapDispatchToProps函数也会在连接组件的props通过浅比较发现改变时重新运行。(第二个参数约定命名为ownProps)。

如果你不提供mapDispatchToProps函数(或action cretors对象)默认的这一行为实现仅是通过向你的组件props注入dispatch

注意:在一些高级场景中,你可能需要更多地对于渲染性能的控制,mapDispatchToProps也可以返回一个方法。在这种情况下,那个所返回的方法会做为一个特定组件实例的最终的mapDispatchToProps。这样一来,你就可以对每个实例进行memoization。可以看PR #279以及上面增加的测试。但大部分应用根本不需要这样做
  • [mergeProps(stateProps,dispatchProps,ownProps):props]:(Function):

如果指明了这个参数,它内部要接收mapStateToProps()mapDispatchToProps()以及父props三个参数。这个函数参数所返回的纯对象将会作为props传递给被包装组件。你可以使用这个函数来根据props选择一部分state,或者把action创建函数绑定在一个特定的props变量上。如果你省略这个参数,则会默认使用Object.assign({}, ownProps, stateProps, dispatchProps)方法。

  • [options](Object):这个参数可以进一步定制connector的行为。除了可以传递给connectAdvanced()的选项(见下),connect()还接受以下额外选项:

    • [pure] (Boolean):若为true,在相关的state/props分别进行了浅比较后发现没有改变的情况下,connect()会避免重新渲染以及对mapStateToPropsmapDispatchToPropsmergeProps的调用。假设我们的被包装组件是一个不依赖任何inputstate的“纯”组件,仅依赖于props和被选中的Redux store state除外。默认值:true
    • [areStatesEqual](Function):当puretrue,比较下一个store state和其先前值。默认值:严格相等(===)
    • [areOwnPropsEqual](Function):当puretrue,比较下一个`props
`和其先前值。默认值:`浅比较`
- [`areStatePropsEqual`](Function):当`pure`为`true`,比较`mapStateToProps
`的结果和其先前值。默认值:`浅比较`
- [`areMergedPropsEqual`](Function):当`pure`为`true`,比较`mergeProps
`的结果和其先前值。默认值:`浅比较`
- [`storeKey`] (String):读取`store`的地方的`context`的`key`。可能你只有在不建议的具有多个`store`的位置才会需要这个。默认值:`store`

mapStateToPropsmapDispatchToProps的参数数量决定了他们是否接收ownProps

注意:在位于前面的mapStateToPropsmapDispatchToProps函数定义时只有一个强制参数(函数长度为1)的情况下,ownProps不会传递给这两个函数。比如,把函数定义为以下形式时不会接受ownProps作为其第二个参数。
function mapStateToProps(state) {
  console.log(state); // state
  console.log(arguments[1]); // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
  console.log(state); // state
  console.log(ownProps); // {}
}

函数如果没有强制参数或者有两个参数则会接收ownProps

const mapStateToProps = (state, ownProps) => {
  console.log(state); // state
  console.log(ownProps); // ownProps
}
function mapStateToProps() {
  console.log(arguments[0]); // state
  console.log(arguments[1]); // ownProps
}
const mapStateToProps = (...args) => {
  console.log(args[0]); // state
  console.log(args[1]); // ownProps
}

option.puretrue时优化connect

option.puretrue时,connect会进行一系列的等性判断来避免不必要的对mapStateToPropsmapDispatchToPropsmergeProps的调用,以及最终的render。这些判断包括areStatesEqual, areOwnPropsEqual, areStatePropsEqual, and areMergedPropsEqual。尽管大多数情况下默认判断都可以适用,但是你也许处于性能或其他原因需要重写这些判断。下面是几个例子:

  • 在你的mapStateToProps函数计算开销大、并且总是关心一小部分的state时,你也许希望重写areStatesEqual。举个例子:areStatesEqual: (next, prev) => prev.entities.todos === next.entities.todos。这样一来,便可以有效地忽略无关的state变化,而只关心你那一小部分state
  • 如果你有使你store state突变的不纯净reducers,那么你可能想重写areStatesEqual以使它总是返回falseareStatesEqual: () => false)。(这样可能会影响你的其他等性判断,取决于你的mapStateToProps函数)
  • 你可能想通过重写areOwnPropsEqual把将来的props列入白名单。你还要实现mapStateToPropsmapDispatchToPropsmergePropsprops列入白名单。(实现方式还可以更简单,比如使用重构的mapProps
  • 如果你的mapStateToProps使用了一种只在一个相关prop变化时才返回一个新对象的memoized selector,你可能想重写areStatePropsEqual使其使用strictEqual。这可能仅是一个很小的性能提升,但却可以省去每一次mapStateToProps调用时对每一个props进行等性判断。
  • 如果你的selectors制造了一个复杂的props,你可能想去重写areMergedPropsEqual来实现deepEqual深比较。比如:嵌套对象,新数组等。(深比较会比直接re-rendering要更快)

返回值

一个高阶React组件类,它能够把stateaction creators传递给由所提供的参数生成的组件中去。这一高阶组件由connectAdvanced生成,这里会把它的详情罗列出来。

实例

仅注入dispatch而不监听store

export default connect()(TodoApp)

注入所有action创建函数(addTodocompleteTodo,...)而不订阅store

import * as actionCreators from './actionCreators'

export default connect(null, actionCreators)(TodoApp)

注入dispatch以及全局state的所有字段

不要这么做!这将会扼杀所有的性能优化,因为TodoApp会在每一次state变化后重新渲染。最好把更多细粒度的connect()用在你的每个展示层里的组件中去监听相关的一小部分state
export default connect(state => state)(TodoApp)

注入dispatchtodos

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps)(TodoApp)

注入todos和所有的action创建函数

import * as actionCreators from './actionCreators'

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps, actionCreators)(TodoApp)

注入todos和所有的action创建函数(addTodocompleteTodo,...)的actions属性形式

import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
  return { actions: bindActionCreators(actionCreators, dispatch) }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

注入todos和特定的action创建函数(addTodo

import { addTodo } from './actionCreators'
import { bindActionCreators } from 'redux'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ addTodo }, dispatch)
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

简写语法注入 todos 和特定的 action 创建函数(addTodo and deleteTodo)

import { addTodo, deleteTodo } from './actionCreators'

function mapStateToProps(state) {
  return { todos: state.todos }
}

const mapDispatchToProps = {
  addTodo,
  deleteTodo
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

注入todos 并把 todoActionCreators 作为 todoActions 属性、counterActionCreators 作为 counterActions 属性注入到组件中

import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
  return {
    todoActions: bindActionCreators(todoActionCreators, dispatch),
    counterActions: bindActionCreators(counterActionCreators, dispatch)
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoApp)

注入todos并把 todoActionCreators 与 counterActionCreators 一同作为 actions 属性注入到组件中

import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(Object.assign({}, todoActionCreators, counterActionCreators), dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

注入 todos 并把所有的 todoActionCreators 和 counterActionCreators 作为 props 注入到组件中

import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(
    Object.assign({}, todoActionCreators, counterActionCreators),
    dispatch
  )
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoApp)

根据组件的 props 注入特定用户的 todos

import * as actionCreators from './actionCreators'

function mapStateToProps(state, ownProps) {
  return { todos: state.todos[ownProps.userId] }
}

export default connect(mapStateToProps)(TodoApp)

根据组件的 props 注入特定用户的 todos 并把 props.userId 传入到 action

import * as actionCreators from './actionCreators'

function mapStateToProps(state) {
  return { todos: state.todos }
}

function mergeProps(stateProps, dispatchProps, ownProps) {
  return Object.assign({}, ownProps, {
    todos: stateProps.todos[ownProps.userId],
    addTodo: text => dispatchProps.addTodo(ownProps.userId, text)
  })
}

export default connect(
  mapStateToProps,
  actionCreators,
  mergeProps
)(TodoApp)

工厂函数

工厂函数可以用来进行性能优化

import {addTodo} from './actionCreators'

function mapStateToPropsFactory(initialState,initialProps){
    const getSomeProperty=createSelector(...);
    const anotherProperty=200+initialState[initialProps.another];
    return function(state){
        return {
            anotherProperty,
            someProperty:getSomeProperty(state),
            todos:state.todos
        }
    }
}

function mapDispatchToPropsFactory(initialState,initialProps){
    function goToSomeLink(){
        initialProps.history.push('some/link');
    }
    return function(dispatch){
        return {
            addTodo
        }
    }
}

export default connect(mapStateToPropsFactory,mapDispatchToPropsFactory)(TodoApp)

connectAdvanced

connectAdvanced(selectorFactory,[connectOptions])

它是一个将 React 组件连接到 Redux store 的函数。这个函数是 connect() 的基础,但是对于如何把state, props, 和 dispatch 组合到最后的 props 中,则不那么自以为是。它不对默认值或结果的记录做任何假设,而是将这些责任留给调用者。

它不修改传递给它的组件类;相反,它返回一个新的、已连接的组件类,供您使用。

参数

  • selectorFactory(dispatch, factoryOptions): selector(state, ownProps): props (Function):初始化选择器函数 (在每个实例的构造函数中)。该选择器函数是在 connector 组件需要重新计算一个新的 props 时调用,作为 store 的 state 改变或者接收到一个新的 props 的结果。selector 的结果应该是一个普通对象,作为被包裹的组件的 props 传递。如果连续调用 selector 都返回与上一次调用相同的对象(===),则不会重新渲染该组件。selector 的责任是在适当的时候返回以前的对象。
  • [connectOptions] (Object) 如果指定,则进一步自定义连接器(connector)的行为。

    • [getDisplayName] (Function):计算连接器组件相对于被包裹的组件的 DisplayName 属性。 通常被包裹函数覆盖。 默认值: name => 'ConnectAdvanced('+name+')'
[methodName] (String):显示在错误消息中。 通常被包裹函数覆盖。 默认值: 'connectAdvanced'

- [`renderCountProp`] (String): 如果被定义, 名为此值的属性将添加到传递给被包裹组件的 props 中。它的值将是组件被渲染的次数,这对于跟踪不必要的重新渲染非常有用。默认值: `undefined`

- [`shouldHandleStateChanges`] (Boolean): 控制连接器(`connector`)组件是否订阅 redux store 的 state 更改。 如果设置为 false,则只会在`componentWillReceiveProps`中重新渲染。 默认值: true

- [`storeKey`] (String): 可以获取 store 的 `props/context` key。 当你不明智地使用了多个 `store` 的时候,你才可能需要这个。默认值: `'store'`

- [`withRef`] (Boolean): 如果为 true,则将一个引用存储到被包裹的组件实例中,并通过 `getWrappedInstance()` 方法使其可用。 默认值:` false`

- 此外,通过 `connectOptions` 传递的任何额外选项都将传递给 `factoryOptions` 参数中的 `selectorFactory`。

返回值

一个高阶 React 组件类,它从 store 的 state 生成 props 并将它们传递给被包裹的组件。高阶组件是接受组件参数并返回新组件的函数。

静态属性

  • WrappedComponent (Component): 原始组件类传递给 connectAdvanced(...)(Component)

静态函数

组件的所有原始静态方法都被挂起。

实例方法

getWrappedInstance(): ReactComponent

返回被包裹组件的实例。只有当你传递 { withRef: true } 作为options 的参数才可用。

注意

  • 因为 connectAdvanced 返回一个高阶组件,所以需要调用它两次。 第一次使用上面描述的参数,第二次使用组件: connectAdvanced(selectorFactory)(MyComponent)
  • connectAdvanced 不修改传递的 React 组件。它返回一个新的连接组件,您应该使用它。

实例

根据 props 将特定用户的 todos 注入,并将 pros.userid 注入到操作中

import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'

function selectorFactory(dispatch) {
  let ownProps = {}
  let result = {}
  const actions = bindActionCreators(actionCreators, dispatch)
  const addTodo = text => actions.addTodo(ownProps.userId, text)
  return (nextState, nextOwnProps) => {
    const todos = nextState.todos[nextOwnProps.userId]
    const nextResult = { ...nextOwnProps, todos, addTodo }
    ownProps = nextOwnProps
    if (!shallowEqual(result, nextResult)) result = nextResult
    return result
  }
}
export default connectAdvanced(selectorFactory)(TodoApp)

createProvider([storeKey])

创建一个新的<Provider>,它将在上下文的传递 key 上设置 Redux Store。 当你不明智地使用了多个 store 的时候,你才可能需要这个。您还需要将相同的 storeKey 传递给connectoptions 参数。

参数

  • [storeKey] (String): 要在其上设置 store 的context的 key。 默认值: 'store'

实例

在创建多个stores之前,请先查看这个FAQ:Can or should I create multiple stores?

import { connect, createProvider } from 'react-redux'

const STORE_KEY = 'componentStore'

export const Provider = createProvider(STORE_KEY)

function connectExtended(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  options = {}
) {
  options.storeKey = STORE_KEY
  return connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    options
  )
}

export { connectExtended as connect }

现在你可以导入上述的Providerconnect来使用他们了。

Provider

<Provider/>

概览

<Provider/>使得每一个被connect()函数包装过的嵌套组件都可以访问到Redux store

既然任何Redux-React app中的React组件都可以被连接,那么大多数应用都会在最顶层渲染<Provider>,从而包裹住整个组件树。

通常,你如果不把已连接组件嵌套在<Provider>中那么你就不能使用它。

注意: 如果实在是需要,你可以手动把store作为一个prop传递给一个连接组件。但是我们仅建议在单元测试中对 store伪造(stub),或者在非完全React代码库中才这么做。通常,你就使用<Provider>吧。

Props

  • store (Redux Store): 应用程序中唯一的 Redux store 对象
  • children (ReactElement) 组件层级的根组件。

实例应用

Vanilla React

ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

React Router

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <Route path="foo" component={Foo}/>
        <Route path="bar" component={Bar}/>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

指南

排错

确保在开始之前学习了Redux排错

我收到以下警告:Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead.

这个警告会在你使用react 15.5.*时出现。基本上,现上它只是一个 warning, 但是在 React16 当中可能会导致你的应用崩溃。现在 PropTypes 应该从 'prop-types' 包中 import,而不是从 react 包中 import。

更新到最新版本的 react-redux。

我的视图不更新啊!

阅读上面的链接。 简而言之,

  • Reducer 永远不应该更改原有 state,应该始终返回新的对象,否则,React Redux 觉察不到数据变化。
  • 确保你使用了 connect()mapDispatchToProps 参数或者 bindActionCreators 来绑定 action creator 函数,你也可以手动调用 dispatch() 进行绑定。直接调用 MyActionCreators.addTodo() 并不会起任何作用,因为它只会返回一个 action 对象,并不会 dispatch 它。

React Router 0.13 的 route 变化中,view 不更新

如果你正在使用 React Router 0.13,你可能会碰到这样的问题。解决方法很简单:当使用 <RouteHandler> 或者 Router.run 提供的 Handler 时,不要忘记传递 router state

根 view:

Router.run(routes, Router.HistoryLocation, (Handler, routerState) => {
// 注意这里的 "routerState" 
  ReactDOM.render(
    <Provider store={store}>
      {/* note "routerState" here */}
      <Handler routerState={routerState} />
    </Provider>,
    document.getElementById('root')
  )
})

嵌套 view

render() {
  // 保持这样传递下去
  return <RouteHandler routerState={this.props.routerState} />
}

很方便地,这样你的组件就能访问 router 的 state 了! 当然,你可以将 React Router 升级到 1.0,这样就不会有此问题了。(如果还有问题,联系我们!)

Redux 外部的一些东西更新时,view 不更新

如果 view 依赖全局的 state 或是 React “context”,你可能发现那些使用 connect() 进行修饰的 view 无法更新。

这是因为,默认情况下 connect() 实现了 shouldComponentUpdate,它假定在 props 和 state 一样的情况下,组件会渲染出同样的结果。这与 React 中 PureRenderMixin 的概念很类似。

这个问题的最好的解决方案是保持组件的纯净,并且所有外部的 state 都应通过 props 传递给它们。这将确保组件只在需要重新渲染时才会重新渲染,这将大大地提高了应用的速度。

当不可抗力导致上述解法无法实现时(比如,你使用了严重依赖 React context 的外部库),你可以设置 connect() 的 pure: false 选项:

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps, null, null, {
  pure: false
})(TodoApp)

这样就表示你的 TodoApp 不是纯净的,只要父组件渲染,自身都会重新渲染。注意,这会降低应用的性能,所以只有在别无他法的情况下才使用它。

在 context 或 props 中都找不到 “store”

如果你有 context 的问题,

  1. 确保你没有引入多个 React 实例到页面上。
  2. 确保你没有忘记将根组件或者其他祖先组件包装进 <Provider>
  3. 确保你运行的 React 和 React Redux 是最新版本。

Invariant Violation:addComponentAsRefTo(...):只有 ReactOwner 才有 refs。这通常意味着你在一个没有 owner 的组件中添加了 ref

如果你在 web 中使用 React,就通常意味着你重复引用了 React。按照这个链接解决即可。

查看原文

阿健666 收藏了文章 · 2019-07-29

Vue面试中,经常会被问到的面试题/Vue知识点整理

看看面试题,只是为了查漏补缺,看看自己那些方面还不懂。切记不要以为背了面试题,就万事大吉了,最好是理解背后的原理,这样面试的时候才能侃侃而谈。不然,稍微有水平的面试官一看就能看出,是否有真才实学还是刚好背中了这道面试题。
(都是一些基础的vue面试题,大神不用浪费时间往下看)

一、对于MVVM的理解?

MVVM 是 Model-View-ViewModel 的缩写。
Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。
View 代表UI 组件,它负责将数据模型转化成UI 展现出来。
ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步View 和 Model的对象,连接Model和View。
在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过ViewModel进行交互,Model 和 ViewModel 之间的交互是双向的, 因此View 数据的变化会同步到Model中,而Model 数据的变化也会立即反应到View 上。
ViewModel 通过双向数据绑定把 View 层和 Model 层连接了起来,而View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
bg2015020110.png

二、Vue的生命周期

beforeCreate(创建前) 在数据观测和初始化事件还未开始
created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来
beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
1.什么是vue生命周期?
答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

2.vue生命周期的作用是什么?
答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

3.vue生命周期总共有几个阶段?
答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

4.第一次页面加载会触发哪几个钩子?
答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。

5.DOM 渲染在 哪个周期中就已经完成?
答:DOM 渲染在 mounted 中就已经完成了。

三、 Vue实现数据双向绑定的原理:Object.defineProperty()

vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。

vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —>视图更新;视图交互变化(input)—>数据model变更双向绑定效果。

js实现简单的双向绑定

<body>
    <div id="app">
    <input type="text" id="txt">
    <p id="show"></p>
</div>
</body>
<script type="text/javascript">
    var obj = {}
    Object.defineProperty(obj, 'txt', {
        get: function () {
            return obj
        },
        set: function (newValue) {
            document.getElementById('txt').value = newValue
            document.getElementById('show').innerHTML = newValue
        }
    })
    document.addEventListener('keyup', function (e) {
        obj.txt = e.target.value
    })
</script>

四、Vue组件间的参数传递

1.父组件与子组件传值
父组件传给子组件:子组件通过props方法接受数据;
子组件传给父组件:$emit方法传递参数
2.非父子组件间的数据传递,兄弟组件传值
eventBus,就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。项目比较小时,用这个比较合适。(虽然也有不少人推荐直接用VUEX,具体来说看需求咯。技术只是手段,目的达到才是王道。)

五、Vue的路由实现:hash模式 和 history模式

hash模式:在浏览器中符号“#”,#以及#后面的字符称之为hash,用window.location.hash读取;
特点:hash虽然在URL中,但不被包括在HTTP请求中;用来指导浏览器动作,对服务端安全无用,hash不会重加载页面。
hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.xxx.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

history模式:history采用HTML5的新特性;且提供了两个新方法:pushState(),replaceState()可以对浏览器历史记录栈进行修改,以及popState事件的监听到状态变更。
history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.xxx.com/items/id。后端如果缺少对 /items/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

六、Vue与Angular以及React的区别?

(版本在不断更新,以下的区别有可能不是很正确。我工作中只用到vue,对angular和react不怎么熟)
1.与AngularJS的区别
相同点:
都支持指令:内置指令和自定义指令;都支持过滤器:内置过滤器和自定义过滤器;都支持双向数据绑定;都不支持低端浏览器。

不同点:
AngularJS的学习成本高,比如增加了Dependency Injection特性,而Vue.js本身提供的API都比较简单、直观;在性能上,AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。

2.与React的区别
相同点:
React采用特殊的JSX语法,Vue.js在组件开发中也推崇编写.vue特殊文件格式,对文件内容都有一些约定,两者都需要编译后使用;中心思想相同:一切都是组件,组件实例之间可以嵌套;都提供合理的钩子函数,可以让开发者定制化地去处理需求;都不内置列数AJAX,Route等功能到核心包,而是以插件的方式加载;在组件开发中都支持mixins的特性。
不同点:
React采用的Virtual DOM会对渲染出来的结果做脏检查;Vue.js在模板中提供了指令,过滤器等,可以非常方便,快捷地操作Virtual DOM。

七、vue路由的钩子函数

首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。

beforeEach主要有3个参数to,from,next:

to:route即将进入的目标路由对象,

from:route当前导航正要离开的路由

next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。

八、vuex是什么?怎么使用?哪种功能场景使用它?

只用来读取的状态集中放在store中; 改变状态的方式是提交mutations,这是个同步的事物; 异步逻辑应该封装在action中。
在main.js引入store,注入。新建了一个目录store,….. export 。
场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车
图片描述

state
Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
mutations
mutations定义的方法动态修改Vuex 的 store 中的状态或数据。
getters
类似vue的计算属性,主要用来过滤一些数据。
action
actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

const store = new Vuex.Store({ //store实例
      state: {
         count: 0
             },
      mutations: {                
         increment (state) {
          state.count++
         }
          },
      actions: { 
         increment (context) {
          context.commit('increment')
   }
 }
})

modules
项目特别复杂的时候,可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
 }
const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
 }

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
})

九、vue-cli如何新增自定义指令?

1.创建局部指令

var app = new Vue({
    el: '#app',
    data: {    
    },
    // 创建指令(可以多个)
    directives: {
        // 指令名称
        dir1: {
            inserted(el) {
                // 指令中第一个参数是当前使用指令的DOM
                console.log(el);
                console.log(arguments);
                // 对DOM进行操作
                el.style.width = '200px';
                el.style.height = '200px';
                el.style.background = '#000';
            }
        }
    }
})

2.全局指令

Vue.directive('dir2', {
    inserted(el) {
        console.log(el);
    }
})

3.指令的使用

<div id="app">
    <div v-dir1></div>
    <div v-dir2></div>
</div>

十、vue如何自定义一个过滤器?

html代码:

<div id="app">
     <input type="text" v-model="msg" />
     {{msg| capitalize }}
</div>

JS代码:

var vm=new Vue({
    el:"#app",
    data:{
        msg:''
    },
    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
})

全局定义过滤器

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

过滤器接收表达式的值 (msg) 作为第一个参数。capitalize 过滤器将会收到 msg的值作为第一个参数。

十一、对keep-alive 的了解?

keep-alive是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
在vue 2.1.0 版本之后,keep-alive新加入了两个属性: include(包含的组件缓存) 与 exclude(排除的组件不缓存,优先级大于include) 。

使用方法

<keep-alive include='include_components' exclude='exclude_components'>
  <component>
    <!-- 该组件是否缓存取决于include和exclude属性 -->
  </component>
</keep-alive>

参数解释
include - 字符串或正则表达式,只有名称匹配的组件会被缓存
exclude - 字符串或正则表达式,任何名称匹配的组件都不会被缓存
include 和 exclude 的属性允许组件有条件地缓存。二者都可以用“,”分隔字符串、正则表达式、数组。当使用正则或者是数组时,要记得使用v-bind 。

使用示例

<!-- 逗号分隔字符串,只有组件a与b被缓存。 -->
<keep-alive include="a,b">
  <component></component>
</keep-alive>

<!-- 正则表达式 (需要使用 v-bind,符合匹配规则的都会被缓存) -->
<keep-alive :include="/a|b/">
  <component></component>
</keep-alive>

<!-- Array (需要使用 v-bind,被包含的都会被缓存) -->
<keep-alive :include="['a', 'b']">
  <component></component>
</keep-alive>

十二、一句话就能回答的面试题

1.css只在当前组件起作用
答:在style标签中写入scoped即可 例如:<style scoped></style>

2.v-if 和 v-show 区别
答:v-if按照条件是否渲染,v-show是display的block或none;

3.$route$router的区别
答:$route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。

4.vue.js的两个核心是什么?
答:数据驱动、组件系统

5.vue几种常用的指令
答:v-for 、 v-if 、v-bind、v-on、v-show、v-else

6.vue常用的修饰符?
答:.prevent: 提交事件不再重载页面;.stop: 阻止单击事件冒泡;.self: 当事件发生在该元素本身而不是子元素的时候会触发;.capture: 事件侦听,事件发生的时候会调用

7.v-on 可以绑定多个方法吗?
答:可以

8.vue中 key 值的作用?
答:当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。key的作用主要是为了高效的更新虚拟DOM。

9.什么是vue的计算属性?
答:在模板中放入太多的逻辑会让模板过重且难以维护,在需要对数据进行复杂处理,且可能多次使用的情况下,尽量采取计算属性的方式。好处:①使得数据处理结构清晰;②依赖于数据,数据更新,处理结果自动更新;③计算属性内部this指向vm实例;④在template调用时,直接写计算属性名即可;⑤常用的是getter方法,获取数据,也可以使用set方法改变数据;⑥相较于methods,不管依赖的数据变不变,methods都会重新计算,但是依赖数据不变的时候computed从缓存中获取,不会重新计算。

10.vue等单页面应用及其优缺点
答:优点:Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件,核心是一个响应的数据绑定系统。MVVM、数据驱动、组件化、轻量、简洁、高效、快速、模块友好。
缺点:不支持低版本的浏览器,最低只支持到IE9;不利于SEO的优化(如果要支持SEO,建议通过服务端来进行渲染组件);第一次加载首页耗时相对长一些;不可以使用浏览器的导航按钮需要自行实现前进、后退。

11.怎么定义 vue-router 的动态路由? 怎么获取传过来的值
答:在 router 目录下的 index.js 文件中,对 path 属性加上 /:id,使用 router 对象的 params.id 获取。

Vue面试中,经常会被问到的面试题/Vue知识点整理
532道前端真实大厂面试题
学习ES6笔记──工作中常用到的ES6语法

查看原文

认证与成就

  • 获得 5 次点赞
  • 获得 26 枚徽章 获得 0 枚金徽章, 获得 8 枚银徽章, 获得 18 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-01-08
个人主页被 494 人浏览