《React与Redux》

128 阅读11分钟

第一章 技术简介

React

React官方: 一个声明式,高效,灵活的,创建用户界面的JavaScript库。

  • 声明式 是指只要使用React描述组件的样子就可以改变用户界面。传统方法是命令式地操作DOM,不仅需要记住大量的API,而且还会增加代码的耦合度,使得项目难以维护。
  • 高效 主要得益于React的虚拟DOM,以及其Diff算法。
  • 灵活 是指React可以作为视图层与其他技术栈配合使用,比如待日Angular的指令,或者与Redux搭配,等等

Redux

Redux 是一个JavaScript状态容器,提供可预测的状态管理

Redux 可以用三条基本原则来描述:单一数据源;state只读;使用纯函数来执行修改

  • 单一数据源 是指整个应用的state被存储在一棵对象树中,并且这个对象树只存在于唯一一个store中,这里的state指的是数据。
  • state只读 并不代表我们无法改变state。“只读”指的是不允许直接对state这个变量重写赋值,但是可以通过action和reducer返回一个新的state
  • 使用纯函数来修改 是指更新state的reducer只是一些纯函数,它接收先前的state和action,并返回state

使用Redux的好处:

  • 可预测:Redux 只是一个数据源,想要修改它只能发起action,reducer又是纯函数,相同的输入永远会得到相同的输出。这一切都使得程序运作变得可控,可预测
  • 便于组织管理代码
  • 支持Universal渲染
  • 优秀的扩展能力 主要是中间件的扩展 例如,Redux Thunk 中间件使Redux的action创建函数具备了异步和条件判断能力 Reducer 的纯函数,不可变,函数组合,中间件的管道,柯里化等这些思想都属于函数式编程的范畴。在Redux中,函数式编程被重度使用。函数式编程使得代码变得更加简洁和模块化

Node与Universal渲染

React与Redux既可以在浏览器端运行也可以在服务器端运行。这里的服务器端指的是Node服务器。

和传统web服务器相比,Node更简单,它是单线程,与平台无关的。最重要的是,它使用了JavaScript这门原来在浏览器中运行的语言,所以我们可以实现Universal渲染--用一套代码在服务端和客户端渲染。

需要同时在服务端和客户端进行渲染,而且最好公用一套代码。于是Universal渲染出现了。

Babel

Babel是一个JavaScript编译器,可以让开发者提前使用下一代的JavaScript.

本书将在Node 和浏览器两个环境中运行React与Redux程序,所以我们需要学习在Node和webpack中使用Babel

Webpack

前端资源模块化管理和打包工具。通过加载器(loader)的转换,任何形式的资源都可以视作模块

总结

React 是一个声明式,高效,灵活的,创建用户界面的JavaScript库,它所带来的Universal渲染更是一场革命。Redux是一个JavaScript状态容器,提供可预测的状态管理,单一数据源,只读state等特性使其从同类工具中脱颖而出。而使用Babel和Webpack做编译和构建则是开发React与Redux程序的最佳选择。

第二章 在Node.js中运行React

编写React 组件

编写React组件通常需要写一个继承来自React.Component类,并在render()中返回你要展示的视图:

import React from 'react'

export default class App extends React.Component {
  render(){
    return (
      <h1>Hello World</h1>
    )
  }
}

两个知识点

  • export default 默认导出这个组件。default表示可在别的文件引入 import App from './App'导入这个模块;如果么有default,则需要import { App } from './App'
  • 然后就是JSX语法 允许在JavaScript中写闭合标签。

第三章 在浏览器中运行React

1.脚手架create react app(创建react应用程序)

还有一个大名鼎鼎的node包管理工具yarn Yarn是弥补了npm的一些缺陷而出现的 虽然npm也进行了改进,但是还是有很多人使用yarn react 默认也是yarn

安装:

  npm install -g yarn
  npm install -g cnpm --registry=https://registry.npm.taobao.org
  npm install -g create-react-app


  通过create-react-app 项目名称
  注意项目名称不要有大写字母
  • 直接通过脚手架把项目搭建起来
  • sudo npm install -g create-react-app (MAC下需加上sudo) 安装好脚手架之后 create-react-app demo01 就可以创建项目了

cd demo01 切换到demo01 目录下 yarn start 启动项目 就可以看到效果了

然后我们在App.js 中修改成,就可以看到 Hello World

import react from 'react'

export default class App extends react.Component{
  render(){
    return (
      <h1>Hello World</h1>
    )
  }
}

在index.js文件中可以看到渲染组件DOM节点中时使用了react-dom的render()方法

第五章 React的创新语法:JSX

class 和 for 又是JavaScript中的保留字。因此在JSX中需要写为className,htmlFor。

JavaScript 表达式

JSX允许在闭合标签中使用JavaScript表达式,但是被{}所包裹。JavaScript表达式要求必须有返回值,因此无法直接使用if else 语句,但是可以使用三元操作表达式以及 || 和 && 这样的比较运算符来书写。如果确实需要使用if else 语句,可以将其写在函数中,然后在{}中调用

import react from 'react'

export default class App extends react.Component {
  render() {
    const name = 'JSX'
    const func = () => {
      let result = "hello";
      if (name) {
        result += name;
      } else {
        result += 'world'
      }
      return result
    }
    return (
      <div>
        <h3>JavaSctipt表达式</h3>
        <p>hello{ name || 'world'}</p>
        <p className = {name?'class-a':'class-b'}>
          hello { name && 'world'}
        </p>
        <p>
          {func()}
        </p>
      </div>
    )
  }
}

结果

JavaSctipt表达式
helloJSX
hello world
helloJSX

样式

属性值不能是字符串,而必须为对象,驼峰命名,例如 font-size变为fontSize

    return (
      <div>
        <h3>样式</h3>
        <p style={{color:'red',fontSize:'14px'}}>
          内联样式不是字符串,而是对象
        </p>
      </div>
    )

注释

在JSX中添加注释,只需要写{}中就可以了

    return (
      <div>
        {/* <h3>注释</h3> */}
        <p style={{color:'red',fontSize:'14px'}}>
          内联样式不是字符串,而是对象
        </p>
      </div>
    )

数组

JSX中的数组会自动展开所有成员。需要加上唯一的key属性。这样是为React的Diff算法服务的

    function Demo5 (){
      const arr = [
        <h3 key={0}>数组</h3>
        <p key={1}>数组会自动展开,数组中每一项元素需要添加key属性</p>
      ];
      return (<li>{arr}</li>)
    }

HTMl标签 vs React组件

而React组件的标签首字母则需要大写

<div>
  <Demo1/>
  <Demo2/>
</div>

第六章 React的数据载体:state、props与context

Props与context则用于组件将传递数据,props仅支持逐层传递,而context则能够跨级传递。State,props与context都是React中的数据载体

State

state被称为内部状态或局部状态

import react from 'react'
export default class Counter extends react.Component{
  constructor(){
    super()
    this.state = {value:0}
  }
  render(){
    return (
      <div>
        <button onClick={()=>this.setState({
          value:this.state.value+1
        })}>increment</button>
        <p>
        Counter 组件内部状态:
        </p>
        <pre>{
          JSON.stringify(this.state,null,2)}
        </pre>
      </div>
    )
  }
}

结果:
increment(按钮)
Counter 组件内部状态:

{
  "value": 0
}

construct是类中的构造函数,它仅在实例化一个类的时候被调用,另外,在子类的构造函数中,必须先调用super(),才能使用this获取实例化对象

Props

顾名思义,props就是属性的意思。我们可以使用props向React组件传递数据,

向一个组件传递props的方法是将数据写在组件标签的属性中

记得组件名首字母大写

<Content value={this.state.value}/>

如何获取:

  • 无状态函数编写的组件 只需要将props作为参数传入组件即可
function Content(props){
  return <p>Content组件的props.value:{props.value}</p>;
}
  • 使用类编写的组件中,需要通过this.props获取props。this 是组件实例

验证props

验证需要使用React。propTypes

组合使用state与props

import React, { Component, PropTypes } from 'react'
function Content(props) {
  return <p>Content组件的props.value:{props.value}</p>
}
Content.PropTypes = {
  value:PropTypes.number.isRequired
}
export default class Counter extends Component{
  constructor(){
    super()
    this.state = {value:0}
  }
  render(){
    return(
      <div>
        <button onClick={()=>this.setState({value:this.state.value+1})}>increment</button>
        <Content value={this.state.value}></Content>
      </div>
    )
  }
}

使用context传递数据

使用context传递数据只需要两步即可实现跨级传递。

  • 将要传递的数据放在消息列表组件的context中
  • 在按钮组件中声明contextTypes,就是可以通过组件context属性访问接收到数据了 只要在一个组件中定义了context,这个组件里面的子组件,不管跨多少级,都可以访问到context中的数据。

Props 与context的适用场景

React的context和全局变量非常相似,在大多数场景下,我们都应该尽量避免使用。适合使用context的场景包括传递登录信息,当前语言以及主题信息等。另外,react-redux的Provider组件就使用了context来传递store

初识redux

Action

  • Action,reducer,state,store是Redux程序中的一些基本概念。
  • Action 本质上是一个JavaScript普通对象。我们约定,action内使用一个字符串类型的type字段来表示要执行的动作。type会被定义成字符串常量。
store.dispatch({type:'INCREMENT'};
store.dispatch({type:'DECREMEN'};

reducer

Reducer 是个形式为(state,action)=> state 的纯函数,描述了action如何把state转变成下一个state

  • "Reducer" 这个名称来源于Array.prototype.reduce(reducer,?initialValue)中的第一个参数reducer.reducer 是一个累加函数,它的参数是上个累加值和数组当前元素,然后通过计算返回本次的累加值。在Redux中,state就是那个累加值,action就是数组当前的元素。
  • reduce

<p>点击按钮计算数组元素相加后的总和。</p>
<button onclick="myFunction()">点我</button>

<p>数组元素总和: <span id="demo"></span></p>

<script>
var numbers = [65, 44, 12, 4];

function getSum(total, num) {
	alert(total)
	alert(num)
    return total + num;
}
function myFunction(item) {
    document.getElementById("demo").innerHTML = numbers.reduce(getSum);
}
</script>
  • reducer
function counter(state=0,action){
  switch(action.type){
    case 'INCREMENT':
     return state + 1;
    case 'RECREMENT':
     return state -1;
    default:
     return state;
}

State 的形式与决于你,可以是基本类型,数组,对象,甚至是Immutabel.js生成的数据结构。唯一的要点是当state变化时需要返回全新的对象,而不是修改传入的参数

为何reducer 要用纯函数?为何不能改变state参数,而要返回一个新的state对象?

纯函数

纯函数(pure Function)是这样一种函数----输入/输出数据流全是显式的。显式的意思是,函数与外界交换数据只有一个唯一渠道--参数和返回值 Reducer就是这样的函数,永远不要在reducer里做这些操作:

  • 修改传入的参数
  • 执行有副作用的操作,如API请求和路由跳转
  • 调用非纯函数,如Date.now()或Math.rendom()

不能修改参数state

在JavaScript中,对象是引用类型,当你改变了参数state,变化前后的两个state将会指向一个地址,react-redux就会以为这是两个相同的state,因而不会不会执行渲染

返回新的对象

会使用Object.assign或者使用Immutable.js 生成不可变数据类型。

Store

Store是个全局的对象,将action和reducer以及state联系在一起。

职能

  • 维持应用的state
  • 提供getState()方法获取state
  • 提供dispatch(action)方法更新state
  • 通过subscribe(listenner)注册监听器

创建

创建store需要从redux包中导入createStore这个方法

import { createStore } from 'redux'

使用reducer纯函数作为第一个参数创建store:

let store = creatStore(counter)

也可以将初始state作为第二个参数传入。

let store = createStore(counter,100)

获取与监听

创建完store,使用它获取数据,并监听变化:

const store = createStore(counter)
let currentValue = store.getState()
store.subscribe(()=>{
  const previousValue = currenValue
  currentValue = store.getState()
  console.log('pre state',previousValue,'next state',currentValue)

发起action

Store使用dispatch(action)方法发起action,更新state

store.dispatch({ type: 'INCREMENT' })

当发起action后,就将action传进了store中,使用reducer纯函数执行更新。

完整代码

import { reateStore } from 'redux'
// 创建纯函数,reducer
function counter(state = 0,action){
  switch(action.type){
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state -1;
    default:
      return state;
   }
}
const store = creatStore(counter)

let currenValue = store.getState()

const listener = ()=>{
  const previousValue = currenValue;
  currenValue = store.getState()
}
store.subscribe(listener)

store.dispatch( { type:'INCREMENT } )

总结

  • Action是个JavaScript对象,它是store数据的唯一来源
  • Reducer 是纯函数,不要在reducer中做:修改传入参数;执行有副作用的操作;调用非纯函数
  • Store负责更新,查阅,订阅state等多个工作。Store是全局唯一的,它将action,reducer,state等联系在一起

第九章 Action创建函数与Redux Thunk 中间件

Action创建函数就是创建action对象的函数,起初它只能返回action对象,但是通过一些中间件的加工后,action创建函数将可以返回更多的类型

action创建函数

编写

function increment(){
  return { type:'INCREMENT'}
}
function decrement(){
  return { type:'DECREMENT'}
}

发起

store.dispatch(increment())

store.dispatch(decrement())

Redux Thunk 中间件

中间件的功能之一就是可以让action创建函数返回更多的格式,以便于编写复杂的逻辑

功能

Redux Thunk 中间件可以让action创建函数先不返回action对象,而是返回一个函数。

function increment(){
  return { type:'INCREMENT }
}
function decrement(){
  return { type:'DECREMENT'}
}
function incrementIfOdd(){
  return (dispath,getState)=>{
    const value = getState();
    if(value % 2 ===0){
      return
    }
    dispatch(increment)
  }
}
function incrementAsync( delay = 1000){
  return dispatch => {
    setTimeout(()=>{
      dispatch(increment())
    },delay)  
  }
}

安装激活

如果你需要在action创建函数中返回函数,执行一些条件判断或者延迟操作,那么首先要安装Redux Thunk 中间件

  • npm install --save redux-thunk
  • 然后引入thunk 和applyMiddleware,并在createStore中激活:
import { createStore,applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
const store = reateStore(counter,applyMiddleware(thunk))

总结

  • Action 创建函数就是创建action函数
  • 如果要发起action创建函数,只需要将其返回结果传给dispatch()。
  • Redux Thunk 中间件可以让action 创建函数先不返回action对象,而是返回一个函数。通过这个函数延迟dispatch或者只在满足指定条件的情况下dispatch
  • 激活Redux Thunk 中间件,只需在createStore中加入applyMiddleware(thunk)即可

第十一章 React与Redux的连接:使用react-redux连接

连接步骤

  • npm install --save react-redux
  • 1.在所有组件的顶层使用Proviver组件给整个程序提供store
import { Provider } from 'reat-redux'

ReactDOM.render(
  <Provider store={store}>
  
  </Provider>,rootEl)
  • 2 使用connect()将state和action创建函数绑定到组件中
import Counter from '../components/Counter
import { connect } from 'react-redux'
import * as ActionCreates from '../actions'

export default connect(
  state => ({ counter:state.counter })
  ActionCreaors
)(Counter)