React.js 小书学习 之 【使用 JSX 描述 UI 信息】
- 从 JSX 到页面 过程图解:
-
JSX <=> JavaScript 对象:
JSX把HTML模板转化为JS对象,使之符合JS语法,所以使用 React 和 JSX 一定会有编译过程 -
React小书总结(提示注意点):
1、JSX 是 JavaScript 语言的一种语法扩展,JSX 元素就是 JavaScript 对象,长得像 HTML,但并不是 HTML。
2、React.js 可以用 JSX 来描述你的组件长什么样的。
3、JSX 在编译的时候会变成相应的 JavaScript 对象描述。
4、react-dom 负责把这个用来描述 UI 信息的 JavaScript 对象变成 DOM 元素,并且渲染到页面上。
React.js 小书学习 之 【组件的 render 方法】
- React.js 中一切皆组件,用 React.js 写的其实就是 React.js 组件;
- 插值表达式{}:{} 内可以放任何 JavaScript 的代码,包括变量、表达式计算、函数执行等等。
React.js 小书学习 之 【组件的组合、嵌套和组件树】
- 自定义的组件都必须要用大写字母开头,普通的 HTML 标签都用小写字母开头;
-
组件树 图解:
- 数据流动:数据在组件树内自上往下流动;
React.js 小书学习 之 【事件监听】
-
事件绑定(
on*={···}
):on*
属性名必须是驼峰命名法;
如:onClick={this.handleClick}
;
注:handleClick,事件监听函数,它是当前组件类自己的一个实例方法 - 通常情况下,
on*
的事件监听只能用在普通的 HTML 的标签上,而不能用在组件标签上,如果需要能用到组件标签上是需要经过特殊处理的; -
event
对象:
1、和普通浏览器一样,事件监听函数会被自动传入一个event
对象,这个对象和普通的浏览器event
对象所包含的方法和属性都基本一致;
2、React.js 将浏览器原生的event
对象封装了一下,对外提供统一的 API 和属性,这样你就不用考虑不同浏览器的兼容性问题; - 关于事件监听函数中的
this
:
1、一般来讲,某个组件类的实例方法里面的this
指向 当前组件类实例本身,
但是 事件监听函数handleClick
里面的this
并不是,直接输出会是null
orundefined
原因是:React.js 对事件监听函数handleClick
的调用并不是通过对象方法的方式调用的,而是直接通过函数调用的,所以 事件监听函数handleClick
内并不能通过this
获取到实例;
2、基于(1),如果我们需要在 事件监听函数handleClick
里面使用当前实例的话,我们需要手动地将实例方法bind
到当前实例上再传入给 React.js;
如:onClick={this.handleClick.bind(this)}
这样就能在 事件监听函数handleClick
内通过this
获取到当前组件类实例了; -
React小书总结(提示注意点):
1、为 React 的组件添加事件监听是很简单的事情,你只需要使用 React.js 提供了一系列的on*
方法即可;
2、React.js 会给每个事件监听传入一个event
对象,这个对象提供的功能和浏览器提供的功能一致,而且它是兼容所有浏览器的;
3、React.js 的事件监听方法需要手动bind
到当前组件类实例,这种模式在 React.js 中非常常用;
React.js 小书学习 之 【组件的 state 和 setState】
- 一个组件的显示形态是可以由它数据状态
state
和配置参数props
共同决定的; -
setState
方法由父类Component
所提供。当我们调用这个函数的时候,React.js 会更新组件的状态state
,并且重新调用render
方法,然后再把render
方法所渲染的最新的内容显示到页面上; - 当我们要改变组件的状态的时候,不能直接用
this.state = xxx
这种方式来修改,如果这样做 React.js 就没办法知道你修改了组件的状态,它也就没有办法更新页面。所以,一定要使用 React.js 提供的setState
方法,它接受一个对象或者函数作为参数; -
setState 合并 :
1、当你调用setState
的时候,React.js 并不会马上修改 state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到state
当中,然后再触发组件更新;
2、在 React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的setState
都进行合并以后再重新渲染组件,所以在使用 React.js 的时候,并不需要担心多次进行setState
会带来性能问题; - 基于(4),setState的合并:
1、setState
方法接受一个对象参数 适用于 仅更新组件状态state
,后续操作不依赖前一个setState
的结果的情况;
2、setState
方法接受一个函数参数 适用于 后续操作依赖前一个setState
的结果的情况,因为 React.js 会把上一个setState
的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新state
的对象:handleClickOnLikeButton () { this.setState((prevState) => { // prevState:上一个 setState 的返回值 return { count: 0 } }) this.setState((prevState) => { // 上一个 setState 的返回是 count 为 0,当前返回 1 return { count: prevState.count + 1 } }) this.setState((prevState) => { // 上一个 setState 的返回是 count 为 1,当前返回 3 return { count: prevState.count + 2 } }) // 最后的结果是 this.state.count 为 3 }
- 更新组件状态
state
,只需要setState
对应状态字段的key
value
即可,无需传入整个对象;constructor (props) { super(props) this.state = { name: 'Tomy', isLiked: false } } handleClickOnLikeButton () { this.setState({ isLiked: !this.state.isLiked }) }
React.js 小书学习 之 【配置组件的 props】
-
React小书总结(提示注意点):
1、为了使得组件的可定制性更强,在使用组件(引入组件)的时候,可以在组件标签上加属性来传入配置参数,我们可以把任何类型的数据作为组件的参数,包括字符串、数字、对象、数组、甚至是函数等等,这样一个组件的样式和行为都可以用props
来控制,就可以达到很好的可配置性;
2、组件可以在内部通过this.props
获取到配置参数,组件可以根据props
的不同来确定自己的样式和行为,达到可配置的效果;
3、可以通过给组件添加类属性defaultProps
来配置默认参数;
4、props
一旦传入,你就不可以在组件内部对它进行修改,但是你可以通过父组件主动重新渲染的方式来传入新的props
,从而达到更新的效果;import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class LikeButton extends Component { // 定义 默认的props配置参数 defaultProps static defaultProps = { wordings: { likedText: '取消', unlikedText: '点赞' } } constructor () { super() this.state = { isLiked: false } } handleClickOnLikeButton () { this.setState({ isLiked: !this.state.isLiked }) // 子组建LikeButton 使用 父组件Index 传递过来的 props配置参数 处理行为 if (this.props.onClick) { this.props.onClick() } } render () { return ( <button onClick={this.handleClickOnLikeButton.bind(this)}> {/* 子组建LikeButton 获取 父组件Index 传递过来的 props配置参数 处理样式,获取后的 props 在子组建中是不可变的,所以就会有在 父组件Index 中的 handleClickOnChange 方法 */} {this.state.isLiked ? this.props.wordings.likedText : this.props.wordings.unlikedText} 👍 </button> ) } } class Index extends Component { constructor () { super() // 父组件Index 中初始化参数 wordings this.state = { wordings: { likedText: '已赞', unlikedText: '赞' } } } // 父组件Index 中处理参数 wordings 的变化,作为处理好的 props 配置参数传递到 子组件LikeButton handleClickOnChange () { this.setState({ wordings: { likedText: '取消', unlikedText: '点赞' } }) } render () { return ( <div> <LikeButton // 使用子组件LikeButton 并传递 配置参数到 子组件LikeButton 的 props // 在这里 wordings 定义子组建样式,传入的是 对象; onClick 定义子组建行为,传入的是 函数 wordings={this.state.wordings} onClick={() => console.log('Click on like button!')}/> <div> <button style={{marginTop: 20 + 'px'}} onClick={this.handleClickOnChange.bind(this)}> 修改 wordings </button> </div> </div> ) } } ReactDOM.render( <Index />, document.getElementById('root') )
React.js 小书学习 之 【state vs props】
- state vs props:http://huziketang.mangojuice.top/books/react/lesson12
注:上面链接已经把两者的区别介绍得比较详尽了,还特意说明了函数式组件的编写方式
React.js 小书学习 之 【渲染列表数据】
如果你往 JSX 插值表达式
{}
中放一个数组,React.js 会帮你把数组里面一个个元素罗列并且渲染出来;-
使用 map 渲染列表数据:
{ users.map( (user, i) => <User key={i} user={user}/> ) }
;
注:这里的key
仅仅只是作为每个元素的唯一标识,虽然写法上同props
传参一样,但并不是props
的一员,不能作为props
在User
这个子组件中使用,若在User
子组件使用,会发生以下错误,其key
值为undefined
:class Lesson extends Component { handleClick (props, e) { console.log(props.key); // undefined, 不能作为 props 使用 console.log(props.index + '-' + props.lesson.title); } render () { const { key, index, lesson } = this.props; return ( <div onClick={this.handleClick.bind(this, { key, index, lesson })}> <h1>{lesson.title}</h1> <p>{lesson.description}</p> </div> ); } }
-
对于用表达式套数组罗列到页面上的元素,都要为每个元素加上
key
属性,这个key
必须是每个元素唯一的标识。标准做法(最好),key
的值可以直接用后台数据返回的id
,因为后台的id
都是唯一的;import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' const users = [ { username: 'Jerry', age: 21, gender: 'male' }, { username: 'Tomy', age: 22, gender: 'male' }, { username: 'Lily', age: 19, gender: 'female' }, { username: 'Lucy', age: 20, gender: 'female' } ] class User extends Component { render () { const { user } = this.props; return ( <div> <div>姓名:{user.username}</div> <div>年龄:{user.age}</div> <div>性别:{user.gender}</div> <hr/> </div> ) } } class Index extends Component { render () { return ( <div> { users.map( (user, i) => <User key={i} user={user}/> ) } </div> ) } } ReactDOM.render( <Index />, document.getElementById('root') )
React.js 小书学习 之 【实战分析:评论功能(一)】
- React.js 中一切都是组件,用 React.js 构建的功能其实也就是由各种组件组合而成;
- 组件的划分没有特别明确的标准,划分组件的目的性是为了代码可复用性、可维护性;
- 我们遵循一个原则:如果一个文件导出的是一个类,那么这个文件名就用大写开头;
-
构建组件树:遵循“自顶而下,逐步求精”的原则,我们从组件的顶层开始,再一步步往下构建组件树;
React.js 小书学习 之 【实战分析:评论功能(二)】
- React.js 认为所有的状态都应该由 React.js 的
state
控制,只要类似于<input />
、<textarea />
、<select />
这样的输入控件被设置了value
值,那么它们的value
值永远以被设置的state
值为准。state
值不变,value
就不会变化; - 类似于
<input />
、<textarea />
、<select />
这些元素的value
值被 React.js 所控制、渲染的组件,在 React.js 当中被称为受控组件(Controlled Component)。对于用户可输入的控件,一般都可以让它们成为受控组件,这是 React.js 所推崇的做法; -
CommentApp
组件将CommentInput
和CommentList
组合起来,它是它们俩的父组件,可以充当桥接两个子组件的桥梁。所以当用户点击发布按钮的时候,我们就将CommentInput
的state
当中最新的评论数据传递给父组件CommentApp
,然后让父组件把这个数据传递给CommentList
进行渲染; -
向父组件传递数据:父组件
CommentApp
只需要通过props
给子组件CommentInput
传入一个回调函数。当用户点击发布按钮的时候,CommentInput
调用props
中的回调函数并且将state
传入该函数即可;
React.js 小书学习 之 【实战分析:评论功能(三)】
- 实现功能之前先理解、分析需求,划分组件。并且掌握划分组件的基本原则——可复用性、可维护性;
-
受控组件的概念,React.js 中的
<input />
、<textarea />
、<select />
等元素的value
值如果是受到 React.js 的控制,那么就是受控组件; - 组件之间使用
props
通过父元素传递数据的技巧:
向父组件传递数据:父组件CommentApp
只需要通过props
给子组件CommentInput
传入一个回调函数。当用户点击发布按钮的时候,CommentInput
调用props
中的回调函数并且将state
传入该函数即可;
React.js 小书学习 之 【前端应用状态管理 — 状态提升】
-
组件间数据共享:我们将组件之间共享的状态交给组件最近的公共父节点保管,然后最近的公共父节点通过
props
把状态(数据)传递给子组件,这样就可以在组件之间共享数据了; - 当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共父组件中去管理,用
props
传递数据或者函数来管理这种依赖或着影响的行为; - React.js 并没有提供好的解决方案来管理这种组件之间的共享状态。在实际项目当中状态提升并不是一个好的解决方案,所以需要引入 Redux 这样的状态管理工具来帮助我们来管理这种共享状态;
React.js 小书学习 之 【挂载阶段的组件生命周期(一)】
- 组件的挂载(初始化组件 -> 挂载到页面上的过程):React.js 将组件渲染,并且构造 DOM 元素然后塞入页面的过程;
- React.js 控制组件在页面上挂载和删除过程的几个方法:
1、componentWillMount
:组件挂载开始之前,也就是在组件调用render
方法之前调用;
2、componentDidMount
:组件挂载完成以后,也就是 DOM 元素已经插入页面后调用;
3、componentWillUnmount
:组件对应的 DOM 元素从页面中删除之前调用;
React.js 小书学习 之 【挂载阶段的组件生命周期(二)】
- 把组件的
state
的初始化工作放在constructor
里面去做; - 在
componentWillMount
进行组件的启动工作,例如 Ajax 数据拉取、定时器的启动; - 有些组件的启动工作是依赖 DOM 的,例如动画的启动,而
componentWillMount
的时候组件还没挂载完成,所以没法进行这些启动工作,这时候就可以把这些操作放在componentDidMount
当中; - 组件从页面上销毁的时候,有时候需要一些数据的清理,例如定时器的清理,就会放在
componentWillUnmount
里面去做;
React.js 小书学习 之 【更新阶段的组件生命周期】
- 组件的挂载指的是将组件渲染并且构造 DOM 元素然后插入页面的过程。这是一个从无到有的过程,React.js 提供一些生命周期函数可以给我们在这个过程中做一些操作;
-
组件更新阶段:说白了就是
setState
导致 React.js 重新渲染组件并且把组件的变化应用到 DOM 元素上的过程,这是一个组件变化的过程; -
更新阶段的组件生命周期:
1、shouldComponentUpdate(nextProps, nextState)
:你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用;
2、componentWillReceiveProps(nextProps)
:组件从父组件接收到新的 props 之前调用;
3、componentWillUpdate()
:组件开始重新渲染之前调用;
4、componentDidUpdate()
:组件重新渲染并且把更改变更到真实的 DOM 以后调用;
React.js 小书学习 之 【ref 和 React.js 中的 DOM 操作】
在 React.js 当中可以直接通过
setState
的方式重新渲染组件,渲染的时候可以把新的props
传递给子组件,从而达到页面更新的效果;React.js 这种重新渲染的机制帮助我们免除了绝大部分的 DOM 更新操作,也让类似于 jQuery 这种以封装 DOM 操作为主的第三方的库从我们的开发工具链中删除;
-
JSX 元素加上
ref
属性:获取挂载完成后的当前 DOM 节点对象,ref
属性值是一个函数,接受一个参数(当前 DOM 节点对象),在函数中我们把这个参数(DOM 元素)设置为组件实例的一个属性,这样以后我们就可以通过this.input
属性获取到这个 DOM 元素了,再配合componentDidMount
生命周期函数使用这个 DOM 元素,并且调用类似this.input.focus()
的 DOM API,进行相关的DOM操作;
注:原则上,能不用 ref 就不用import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class AutoFocusInput extends Component { componentDidMount () { // DOM 节点加载完,才能调用 DOM API, // 所以放在 componentDidMount 生命周期里 this.input.focus() } render () { return ( <div> <input ref={(input) => this.input = input} /> {/* 组件标签也可以加上 ref 获取到的是这个 Clock 组件在 React.js 内部初始化的实例 但这并不是什么常用的做法,而且也并不建议这么做 */} {/* <Clock ref={(clock) => this.clock = clock} /> */} </div> ) } } ReactDOM.render( <AutoFocusInput />, document.getElementById('root') )
React.js 小书学习 之 【props.children 和容器类组件】
- 如果组件标签也能像普通的 HTML 标签那样编写内嵌的结构,那么就方便很多了。实际上,React.js 默认就支持这种写法,所有嵌套在组件中的 JSX 结构都可以在组件内部通过
props.children
(是一个数组)获取到;
React.js 小书学习 之 【dangerouslySetHTML 和 style 属性】
出于安全考虑的原因(XSS 攻击),在 React.js 当中所有的表达式插入的内容都会被自动转义成文本形式,就相当于 jQuery 里面的
text(…)
函数一样,任何的 HTML 格式都会被转义成其对应的文本形式渲染,也就是说,表达式插入的内容中的HTML结构渲染是会被原样输出,不会采用HTML语法格式渲染;-
React.js 提供了一个属性
dangerouslySetInnerHTML
,可以让我们设置动态设置元素的 innerHTML;import React, { Component } from 'react' import ReactDOM from 'react-dom' import './index.css' class Editor extends Component { constructor() { super() this.state = { content: '<h1>React.js 小书</h1>' } } render () { return ( <div> {/* 正确 动态设置元素的 innerHTML */} <div className='editor-wrapper' dangerouslySetInnerHTML={{__html: this.state.content}} /> {/* 错误 动态设置元素的 innerHTML */} <div className='editor-wrapper'> {this.state.content} </div> </div> ) } } ReactDOM.render( <Editor />, document.getElementById('root') )
因为设置
innerHTML
可能会导致跨站脚本攻击(XSS),所以 React.js 团队认为把事情搞复杂可以防止(警示)大家滥用这个属性,dangerouslySetInnerHTML
这个属性不必要的情况就不要使用;-
style属性:
style
接受一个对象,这个对象里面是这个元素的 CSS 属性键值对,原来 CSS 属性中带-
的元素都必须要去掉-
换成驼峰命名,如font-size
换成fontSize
,text-align
换成textAlign
;<h1 style={{fontSize: '12px', color: this.state.color}}>React.js 小书</h1>
React.js 小书学习 之 【PropTypes 和组件参数验证】
React.js 就提供了一种机制,让你可以给组件的配置参数加上类型验证,如果传进来的配置参数类型不符合要求,便会强制报错;
组件参数验证 依赖于 React 提供的第三方库
prop-types
,安装命令如下:
npm:npm install --save prop-types
yarn:yarn add prop-types
-
使用如下:
import React, { Component } from 'react'; import PropTypes from 'prop-types'; // 引入参数校验依赖 class Comment extends Component { // 定义组件类 参数类型属性 static propTypes = { // 定义对应参数类型: object-对象; isRequired-必须传值 // 必传 对象类型 的参数 comment: PropTypes.object.isRequired } render () { const { comment } = this.props return ( <div className='comment'> <div className='comment-user'> <span>{comment.username} </span>: </div> <p>{comment.content}</p> </div> ) } }
-
React.js 提供的
PropTypes
提供了一系列的数据类型可以用来配置组件的参数:
通过
PropTypes
给组件的参数做类型限制,可以在帮助我们迅速定位错误,这在构建大型应用程序的时候特别有用;另外,给组件加上propTypes
,也让组件的开发、使用更加规范清晰;
React.js 小书学习 之 【实战分析:评论功能(四)】
- 数据加载操作等不依赖 DOM 操作的组件启动的操作都可以放在
componentWillMount
中进行; - React 小书 小贴士(1):组件的私有方法都用
_
开头,所有事件监听的方法都用handle
开头。把事件监听方法传给组件的时候,属性名用on
开头; - React 小书 小贴士(2):组件的内容编写顺序(推荐)
1、static
开头的组件类属性,如defaultProps
、propTypes
;
2、构造函数,constructor
;
3、getter/setter
方法;
4、组件生命周期;
5、_
开头的私有方法;
6、事件监听方法,handle*
;
7、render*
开头的方法,有时候render()
方法里面的内容会分开到不同函数里面进行,这些函数都以render*
开头;
8、render()
方法;
React.js 小书学习 之 【实战分析:评论功能(五)】
暂无
React.js 小书学习 之 【实战分析:评论功能(六)】
-
dangerouslySetInnerHTML
属性:动态设置元素的innerHTML
,该属性能不用就不用,易造成 XSS 跨站脚本攻击漏洞,如果用的话,也应该对相应的 HTML 字符进行转义处理,如下:/* ``符号 包含的内容转 <code></code> 处理 */ <p dangerouslySetInnerHTML={{ __html: this._getProcessedContent(this.props.comment.content) }} /> /** * ``符号 包含的内容转 <code></code> 处理 */ _getProcessedContent (content) { return content .replace(/&/g, "&") .replace(/</g, "<") .replace(/>/g, ">") .replace(/"/g, """) .replace(/'/g, "'") .replace(/`([\S\s]+?)`/g, '<code>$1</code>'); }
React.js 小书学习 之 【高阶组件(Higher-Order Components)】
-
高阶组件:高阶组件是一个函数(而不是组件),它接收一个子组件作为参数,返回一个新组件。新组件会使用传入的组件作为子组件;
const NewComponent = higherOrderComponent(OldComponent);
高阶组件的作用其实不言而喻,其实就是为了组件之间的代码复用。组件可能有着某些相同的逻辑,把这些逻辑抽离出来,放到高阶组件中进行复用。高阶组件内部的包装组件和被包装组件之间通过
props
传递数据;-
用法示例:
// 第一个文件 定义高阶组件: 高阶组件就是一个函数,接收一个子组件作为参数,返回一个新组件 import React, { Component } from 'react' export default (WrappedComponent, name) => { class NewComponent extends Component { constructor () { super() this.state = { data: null } } componentWillMount () { let data = localStorage.getItem(name) this.setState({ data }) } render () { return <WrappedComponent data={this.state.data} /> } } return NewComponent } // 第二个文件 使用高阶组件(调用高阶组件函数): 创建高阶组件处理后的新组件并导出 import wrapWithLoadData from './wrapWithLoadData' // 从 ./wrapWithLoadData文件 引入高阶组件 class InputWithUserName extends Component { render () { return <input value={this.props.data} /> } } InputWithUserName = wrapWithLoadData(InputWithUserName, 'username') export default InputWithUserName // 第三个文件 使用被高阶组件加工过的导出后的新组件 import InputWithUserName from './InputWithUserName' // 从 ./InputWithUserName文件 引入 InputWithUserName 组件 class Index extends Component { render () { return ( <div> 用户名:<InputWithUserName /> </div> ) } }
-
多层高阶组件数据流向,如图:
要求:需要先从 LocalStorage 中加载数据,再用这个数据去服务器取数据
React.js 小书学习 之 【React.js 的 context】
- 除非你觉得自己的 React.js 水平已经比较炉火纯青了,否则你永远不要使用
context
,只需要用好这些第三方的应用状态管理库就行了,如:Redux
。就像你学 JavaScript 的时候,总是会被提醒不要用全局变量一样,React.js 的context
其实就像是组件树上某颗子树的全局变量; - React.js 的
context
:某个组件只要往自己的context
里面放了某些状态,这个组件之下的所有子组件都直接访问这个状态而不需要通过中间组件的传递。一个组件的context
只有它的子组件能够访问,它的父组件是不能访问到的,你可以理解每个组件的context
就是瀑布的源头,只能往下流不能往上飞; -
React小书总结(提示注意点):
1、一个组件可以通过getChildContext
方法返回一个对象,这个对象就是子树的 context,提供 context 的组件必须提供childContextTypes
作为 context 的声明和验证。
2、如果一个组件设置了 context,那么它的子组件都可以直接访问到里面的内容,它就像这个组件为根的子树的全局变量。任意深度的子组件都可以通过contextTypes
来声明你想要的 context 里面的哪些状态,然后可以通过this.context
访问到那些状态。
3、context 打破了组件和组件之间通过props
传递数据的规范,极大地增强了组件之间的耦合性。而且,就如全局变量一样,context 里面的数据能被随意接触就能被随意修改,每个组件都能够改 context 里面的内容会导致程序的运行不可预料。
React.js 小书学习 之 【动手实现 Redux(一):优雅地修改共享状态】
- Redux 和 React-redux 并不是同一个东西。Redux 是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。而 React-redux 就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现;
- 定义
dispatch
中间处理函数,它专门负责数据的修改,所有对数据的操作必须通过dispatch
函数。它接受一个参数action
,这个action
是一个普通的 JavaScript 对象,里面必须包含一个type
字段来声明你到底想干什么。dispatch
在swtich
里面会识别这个type
字段,能够识别出来的操作才会执行对共享状态appState
的修改;
React.js 小书学习 之 【动手实现 Redux(二):抽离 store 和监控数据变化】
针对每个不同的 App,我们可以给
createStore
传入初始的共享状态数据appState
和一个描述数据变化的函数stateChanger
,然后生成一个store
。需要修改数据的时候通过store.dispatch
,需要获取数据的时候通过store.getState
;通用的
createStore
,它可以产生一种我们新定义的数据类型store
,通过store.getState
我们获取共享状态,而且我们约定只能通过store.dispatch
修改共享状态。store
也允许我们通过store.subscribe
监听数据数据状态被修改了,并且进行后续的例如重新渲染页面的操作;-
本节完整代码示例如下:
function createStore(state, stateChanger) { const listeners = [] const subscribe = (listener) => listeners.push(listener) const getState = () => state const dispatch = (action) => { stateChanger(state, action) listeners.forEach((listener) => listener()) } return { getState, dispatch, subscribe } } function renderApp(appState) { renderTitle(appState.title) renderContent(appState.content) } function renderTitle(title) { const titleDOM = document.getElementById('title') titleDOM.innerHTML = title.text titleDOM.style.color = title.color } function renderContent(content) { const contentDOM = document.getElementById('content') contentDOM.innerHTML = content.text contentDOM.style.color = content.color } let appState = { title: { text: 'React.js 小书', color: 'red', }, content: { text: 'React.js 小书内容', color: 'blue' } } function stateChanger(state, action) { switch (action.type) { case 'UPDATE_TITLE_TEXT': state.title.text = action.text break case 'UPDATE_TITLE_COLOR': state.title.color = action.color break default: break } } const store = createStore(appState, stateChanger) store.subscribe(() => renderApp(store.getState())) // 监听数据变化 renderApp(store.getState()) // 首次渲染页面 store.dispatch({ // 修改标题文本 type: 'UPDATE_TITLE_TEXT', text: '《React.js 小书》' }) store.dispatch({ // 修改标题颜色 type: 'UPDATE_TITLE_COLOR', color: 'blue' })
React.js 小书学习 之 【动手实现 Redux(三):纯函数(Pure Function)简介】
- 纯函数(Pure Function): 一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数;
- 一个函数执行过程产生了外部可观察的变化那么就说这个函数是有副作用的;
- 外部可观察的变化:除了修改外部的变量,一个函数在执行过程中还有很多方式产生外部可观察的变化,比如说调用 DOM API 修改页面,或者你发送了 Ajax 请求,还有调用 window.reload 刷新浏览器,甚至是 console.log 往控制台打印数据也是副作用;
- 纯函数很严格,也就是说你几乎除了计算数据以外什么都不能干,计算的时候还不能依赖除了函数参数以外的数据;