第一章 技术简介
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)