前端组件化
一个简单的点赞功能
结构复用
实现简单的组件化
状态改变->构建新的DOM元素更新页面->重新插入新的DOM元素
搭建开发环境
// 安装create-react-app工具(一键生成所需要的工程目录,并做好各种配置和依赖)
// 工具地址:https://github.com/facebookincubator/create-react-app
npm install -g create-react-app
// 把npm的源改成taobao的源
npm config set registry https://registry.npm.taobao.org
// 通过create-react-app命令创建一个名为hello-react的工程
create-react-app hello-react
// 进入工程目录然后通过npm启动工程
cd hello-react
npm start
// http://localhost:3000/
// http://192.168.1.11:3000/
使用JSX描述UI信息
JSX原理
要点:
1.每个DOM元素的结构都可以用JS对象来表示。
2.编译的过程会把类似HTML的JSX结构转换成JS的对象结构。
3.JSX是JS语言的一种语法扩展。
组件的render方法
表达式插入
要点:
1.{}内可以放任何JS的代码。
2.表达式插入不但可以用在标签内部,也可以用在标签的属性上。
3.如果插入的表达式返回null,那么React会什么都不显示,相当于忽略了该表达式的插入。结合条件返回,可以做到显示或隐藏某些元素。
条件返回
{}内也可以放置JSX。
JSX元素变量(即JS对象)
可赋值给变量、作为函数参数传递、作为函数的返回值。
组件的组合、嵌套和组件树
事件监听
要点:
1.React封装了一系列的on*的属性(若没有经过处理,这些on*的事件监听只能用在普通的HTML的标签上,而不能用在组件标签上),地址:https://facebook.github.io/react/docs/events.html#supported-events。
2.事件属性名都必须要用驼峰命名法。
event对象
和普通浏览器一样,事件监听函数会被自动传入一个event对象(event对象并不是由浏览器所提供,而是由react内部所创建)。
关于事件中的this
要点:
1.React调用你所传给它的方法的时候,并不是通过对象方法的方式调用(this.handleClick),而是直接通过函数调用(handleClick),所以事件监听函数内并不能通过this获取到实例。
2.如果你想在事件函数当中使用当前的实例,你需要手动地将实例方法bind到当前实例上再传入给React的onClick事件监听,也可以在bind的时候给事件监听函数传入一些参数。
组件的state和setState
state
一个组件的显示形态是可以由它数据状态和配置参数决定的。
setState接收对象参数
要点:
1.setState方法由父类所提供。当我们调用这个函数的时候,React会更新组件的状态state,并且重新调用render方法,然后再把render方法所渲染的最新的内容显示到页面上。
2.接收一个对象或者函数作为参数。
3.传入一个对象的时候,这个对象表示该组件的新状态。但你只需要传入需要给更新的部分就可以了,而不需要传入整个对象。
setState接收函数参数
当你调用setState的时候,React并不会马上修改state。而是把这个对象放到一个更新队列里面,稍后才会从队列当中把新的状态提取出来合并到state当中,然后再触发组件更新。
setState合并
React内部会把JS事件循环中的消息队列的同一个消息中的setState都进行合并以后再重新渲染组件,因此不用担心多次进行setState会带来性能问题。
配置组件的props
要点:
1.每个组件都接收一个props参数,它是一个对象,包含了所有你对这个组件的配置。
2.组件内部通过this.props的方式获取到组件的参数。
3.在使用一个组件的时候,可以把参数放在标签的属性当中,所有的属性都会作为props对象的键值。
4.可以把任何类型的数据作为组件的参数,包括字符串、数字、对象、数组、甚至是函数等等。
默认配置defaultProps
里面是对props中各个属性的默认配置。
props不可变
props一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的props,从而达到更新的效果。
state vs props
要点:
1.state的主要作用是用于组件保存、控制、修改自己的可变状态。(state是让组件控制自己的状态)
2.props的主要作用是让使用该组件的父组件可以传入参数来配置该组件。(props是让外部对组件自己进行配置)
3.尽量少用state,尽量多用props。
4.没有state的组件叫无状态组件,设置了state的叫做有状态组件。
渲染列表数据
渲染存放JSX元素的数组
使用map渲染列表数据
实战分析:评论功能(一)
组件划分
React中一切都是组件,用React构建的功能其实就是由各种组件组合而成。
组件实现
// 构建一个新的工程目录
create-react-app comment-app
// 在工程目录下的src/目录下新建四个文件
CommentApp.js
CommentInput.js
CommentList.js
Comment.js
// CommentApp.js
import React,{ Component } from 'react';
import CommentInput from './CommentInput';
import CommentList from './CommentList';
class CommentApp extends React.Component {
render() {
return (
)
}
}
export default CommentApp
// CommentInput.js
import React,{ Component } from 'react';
class CommentInput extends React.Component {
render() {
return (
CommentInput
)
}
}
export default CommentInput
// CommentList.js
import React,{ Component } from 'react';
class CommentList extends React.Component {
render() {
return (
CommentList
)
}
}
export default CommentList
// 修改src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import CommentApp from './CommentApp';
import './index.css';
ReactDOM.render(
,
document.getElementById('root')
)
// 进入工程目录启动工程
npm run start
添加样式
// 修改CommentApp中的render方法,给它添加一个wrapper类名
import React,{ Component } from 'react';
import CommentInput from './CommentInput';
import CommentList from './CommentList';
class CommentApp extends React.Component {
render() {
return (
)
}
}
export default CommentApp
// index.css
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-color: #fbfbfb;
}
.wrapper {
width: 500px;
margin: 10px auto;
font-size: 14px;
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
}
实战分析:评论功能(二)
处理用户输入
// 修改ComponentInput.js
import React,{ Component } from 'react';
class CommentInput extends React.Component {
render() {
return (
用户名:
评论内容:
)
}
}
export default CommentInput
// 修改index.css
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-color: #fbfbfb;
}
.wrapper {
width: 500px;
margin: 10px auto;
font-size: 14px;
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
}
/* 评论框样式 */
.comment-input {
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
margin-bottom: 10px;
}
.comment-field {
margin-bottom: 15px;
display: flex;
}
.comment-field .comment-field-name {
display: flex;
flex-basis: 100px;
font-size: 14px;
}
.comment-field .comment-field-input {
display: flex;
flex: 1;
}
.comment-field-input input,
.comment-field-input textarea {
border: 1px solid #e6e6e6;
border-radius: 3px;
padding: 5px;
outline: none;
font-size: 14px;
resize: none;
flex: 1;
}
.comment-field-input textarea {
height: 100px;
}
.comment-field-button {
display: flex;
justify-content: flex-end;
}
.comment-field-button button {
padding: 5px 10px;
width: 80px;
border: none;
border-radius: 3px;
background-color: #00a3cf;
color: #fff;
outline: none;
cursor: pointer;
}
.comment-field-button button:active {
background: #13c1f1;
}
/* 评论列表样式 */
.comment-list {
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
}
/* 评论组件样式 */
.comment {
display: flex;
border-bottom: 1px solid #f1f1f1;
margin-bottom: 10px;
padding-bottom: 10px;
min-height: 50px;
}
.comment .comment-user {
flex-shrink: 0;
}
.comment span {
color: #00a3cf;
font-style: italic;
}
.comment p {
margin: 0;
/*text-indent: 2em;*/
}
// 加入处理逻辑
import React,{ Component } from 'react'
class CommentInput extends Component {
constructor() {
super();
this.state={
username:'',
content:''
};
}
handleUsernameChange(event) {
this.setState({
username:event.target.value
});
}
handleContentChange(event) {
this.setState({
content:event.target.value
});
}
handleSubmit() {
if(this.props.onSubmit) {
const {username,content}=this.state;
this.props.onSubmit({username,content});
};
this.setState({content:''});
}
render() {
return (
用户名:
评论内容:
)
}
}
export default CommentInput
// 要点:
1.、、这样的输入控件被设置了value值,那么它们的值永远以被设置的值为准,值不变,value就不会变化。
2.类似于、、
向父组件传递数据
// 修改CommentApp.js
import React,{ Component } from 'react'
import CommentInput from './CommentInput'
import CommentList from './CommentList'
class CommentApp extends Component {
handleSubmitComment(comment) {
console.log(comment);
}
render() {
return (
);
}
}
export default CommentApp
实战分析:评论功能(三)
// 修改CommentList.js
import React,{ Component } from 'react'
class CommentList extends Component {
render() {
const comments=[
{
username:'osoLife',
content:'i'
},
{
username:'moli',
content:'love'
},
{
username:'michael',
content:'react'
}
];
return (
{
comments.map(
(comment,i)=>{
return (
{comment.username}:{comment.content}
)
}
)
}
);
}
}
export default CommentList
// 修改Comment.js
import React,{ Component } from 'react';
class Comment extends Component {
render() {
return (
{this.props.comment.username} :
{this.props.comment.content}
)
}
}
export default Comment
// 修改CommentList.js
import React,{ Component } from 'react';
import Comment from './Comment';
class CommentList extends Component {
// 防止comments不传入的情况
static defaultProps={
comments:[]
}
// 删除测试数据,改成props获取评论数据
render() {
return (
{
this.props.comments.map(
(comment,i)=>
)
}
);
}
}
export default CommentList
// 修改CommentApp.js
import React,{ Component } from 'react'
import CommentInput from './CommentInput'
import CommentList from './CommentList'
class CommentApp extends Component {
constructor() {
super();
this.state={
comments:[]
}
}
handleSubmitComment(comment) {
this.state.comments.push(comment);
this.setState({
comments:this.state.comments
})
}
render() {
return (
);
}
}
export default CommentApp
// 给handleSubmitComment加入简单的数据检查
handleSubmitComment(comment) {
if(!comment) {
return;
}
if(!comment.username) {
return alert('请输入用户名');
}
if(!comment.content) {
return alert('请输入评论内容');
}
this.state.comments.push(comment);
this.setState({
comments:this.state.comments
});
}
前端应用状态管理——状态提升
当某个状态被多个组件依赖或者影响的时候,就把该状态提升到这些组件的最近公共组件中去管理,用props传递数据或者函数来管理这种依赖或者影响的行为。
挂载阶段的组件生命周期(一)
React将组件渲染,并且构造DOM元素然后塞入页面的过程称为组件的挂载。
// 一个组件的方法调用过程
-> constructor()
-> render()
// 然后构造DOM元素插入页面
-> constructor()
-> componentWillMount()
-> render()
// 然后构造DOM元素插入页面
-> componentDidMount()
// ...
// 即将从页面中删除
-> componentWillUnmount()
// 从页面中删除
挂载阶段的组件生命周期(二)
更新阶段的组件生命周期
// 组件的挂载指的是将组件渲染并且构造DOM元素然后插入页面的过程,这是一个从无到有的过程。React提供一些生命周期函数可以给我们在这个过程中做一些操作。除了挂载阶段,还有一种“更新阶段”。
// 更新阶段的组件生命周期:
1.shouldComponentUpdate(nextProps,nextState):通过这个方法可以控制组件是否重新渲染,如果返回false组件就不会重新渲染。
2.componentWillReceiveProps(nextProps):组件从父组件接收到新的props之前调用。
3.componentWillUpdate():组件开始重新渲染之前调用。
4.componentDidUpdate():组件重新渲染并且把更改变更到真实的DOM以后调用。
ref和React中的DOM操作
// 通过ref属性来获取已经挂载的元素的DOM节点
// 原则:能不用ref就不用
props.children和容器类组件
// React会把嵌套的JSX元素一个个都放到数组当中,然后通过props.children传给了Card。
// 由于JSX会把插入表达式里面数组中的JSX一个个罗列下来显示。
// 所有嵌套在组件中的JSX结构都可以在组件内部通过props.children获取到。
// 在组件内部把数组中的JSX元素安置在不同的地方示例:
dangerouslySetHTML和style属性
dangerouslySetHTML
// 出于安全考虑的原因(XSS攻击),在React当中所有的表达式插入的内容都会被自动转义。
// 通过dangerouslySetInnerHTML属性动态设置元素的innerHTML(给dangerouslySetInnerHTML传入一个对象,这个对象的__html属性值就相当于元素的innerHTML)
style
// 需要把CSS属性变成一个对象再传给元素(原来CSS属性中带“-”的元素都必须去掉“-”换成驼峰命名)
osoLife
PropTypes和组件参数验证
// 安装第三方库:prop-types
npm install --save prop-types
// 给组件的配置参数加上类型验证
import React,{ Component } from 'react';
import PropTypes from 'prop-types';
class Comment extends Component {
static propTypes={
comment:PropTypes.object
}
render() {
const { comment }=this.props;
return (
{comment.username} :
{comment.content}
)
}
}
export default Comment
// 可选参数我们可以通过配置defaultProps,让它在不传入的时候有默认值。
import React,{ Component } from 'react';
import PropTypes from 'prop-types';
class Comment extends Component {
// 通过isRequired关键字来强制组件某个参数必须传入
static propTypes={
comment:PropTypes.object.isRequired
}
render() {
const { comment }=this.props;
return (
{comment.username} :
{comment.content}
)
}
}
export default Comment
// PropTypes提供了一系列的数据类型可以用来配置组件的参数
PropTypes.array
PropTypes.bool
PropTypes.func
PropTypes.number
PropTypes.object
PropTypes.string
PropTypes.node
PropTypes.element
...
实战分析:评论功能(四)
// index.css
body {
margin: 0;
padding: 0;
font-family: sans-serif;
background-color: #fbfbfb;
}
.wrapper {
width: 500px;
margin: 10px auto;
font-size: 14px;
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
}
/* 评论框样式 */
.comment-input {
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
margin-bottom: 10px;
}
.comment-field {
margin-bottom: 15px;
display: flex;
}
.comment-field .comment-field-name {
display: flex;
flex-basis: 100px;
font-size: 14px;
}
.comment-field .comment-field-input {
display: flex;
flex: 1;
}
.comment-field-input input,
.comment-field-input textarea {
border: 1px solid #e6e6e6;
border-radius: 3px;
padding: 5px;
outline: none;
font-size: 14px;
resize: none;
flex: 1;
}
.comment-field-input textarea {
height: 100px;
}
.comment-field-button {
display: flex;
justify-content: flex-end;
}
.comment-field-button button {
padding: 5px 10px;
width: 80px;
border: none;
border-radius: 3px;
background-color: #00a3cf;
color: #fff;
outline: none;
cursor: pointer;
}
.comment-field-button button:active {
background: #13c1f1;
}
/* 评论列表样式 */
.comment-list {
background-color: #fff;
border: 1px solid #f1f1f1;
padding: 20px;
}
/* 评论组件样式 */
.comment {
position: relative;
display: flex;
border-bottom: 1px solid #f1f1f1;
margin-bottom: 10px;
padding-bottom: 10px;
min-height: 50px;
}
.comment .comment-user {
flex-shrink: 0;
}
.comment-username {
color: #00a3cf;
font-style: italic;
}
.comment-createdtime {
padding-right: 5px;
position: absolute;
bottom: 0;
right: 0;
padding: 5px;
font-size: 12px;
}
.comment:hover .comment-delete {
color: #00a3cf;
}
.comment-delete {
position: absolute;
right: 0;
top: 0;
color: transparent;
font-size: 12px;
cursor: pointer;
}
.comment p {
margin: 0;
/*text-indent: 2em;*/
}
code {
border: 1px solid #ccc;
background: #f9f9f9;
padding: 0px 2px;
}
自动聚焦到评论框
// 修改CommentInput.js
// 给输入框元素加上ref以便获取到DOM元素
...
持久化用户名
// 监听用户名输入框失去焦点事件onBlur
// 在handleUsernameBlur中把用户的输入内容保存到LocalStorage当中
// 在handleUsernameBlur中把用户输入的内容传给_saveUsername私有方法(所有私有方法都以_开头)。
_saveUsername(username) {
localStorage.setItem('username',username)
}
handleUsernameBlur(event) {
this._saveUsername(event.target.value)
}
handleUsernameChange(event) {
this.setState({
username:event.target.value
});
}
componentWillMount() {
this._loadUsername()
}
_loadUsername() {
const username=localStorage.getItem('username')
if(username) {
this.setState({
username
})
}
}
// 规范:
组件的私有方法都用“_”开头,所有事件监听的方法都用handle开头,把事件监听方法传给组件的时候,属性名用on开头。
// 组件的内容编写顺序:
1.static开头的类属性,如defaultProps、propTypes。
2.构造函数,constructor。
3.getter/setter。
4.组件生命周期。
5._开头的私有方法。
6.事件监听方法,handle*。
7.render*开头的方法,有时候render()方法里面的内容会分开到不同函数里面进行,这些函数都以render*开头。
8.render()方法。
完整代码
// CommentApp.js
import React, { Component } from 'react';
import CommentInput from './CommentInput';
import CommentList from './CommentList';
class CommentApp extends Component {
constructor() {
super();
this.state = {
comments: []
};
}
handleSubmitComment(comment) {
if(!comment) {
return;
}
if (!comment.username) {
return alert('请输入用户名');
}
if (!comment.content) {
return alert('请输入评论内容');
}
this.state.comments.push(comment);
this.setState({
comments: this.state.comments
});
}
render() {
return (
)
}
}
export default CommentApp
// CommentInput.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class CommentInput extends Component {
static propTypes={
onSubmit:PropTypes.func
}
constructor() {
super();
this.state={
username: '',
content: ''
};
}
componentWillMount() {
this._loadUsername();
}
componentDidMount() {
this.textarea.focus();
}
_saveUsername(username) {
localStorage.setItem('username',username);
}
_loadUsername() {
const username=localStorage.getItem('username');
if(username) {
this.setState({
username
})
};
}
handleUsernameChange(event) {
this.setState({
username: event.target.value
})
}
handleUsernameBlur(event) {
this._saveUsername(event.target.value)
}
handleContentChange(event) {
this.setState({
content: event.target.value
})
}
handleSubmit() {
if (this.props.onSubmit) {
this.props.onSubmit({
username: this.state.username,
content: this.state.content,
})
}
this.setState({ content: '' })
}
render() {
return (
用户名:
评论内容:
)
}
}
export default CommentInput
// CommentList.js
import React, { Component } from 'react';
import Comment from './Comment';
class CommentList extends Component {
static defaultProps={
comments: []
}
render() {
return (
{this.props.comments.map((comment, i) =>
)}
)
}
}
export default CommentList
// Comment.js
import React,{ Component } from 'react';
class Comment extends Component {
render() {
return (
{this.props.comment.username} :
{this.props.comment.content}
)
}
}
export default Comment
实战分析:评论功能(五)
持久化评论
高阶组件
// 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件
// 高阶组件的作用是用于代码复用,可以把组件之间可复用的代码、逻辑抽离到高阶组件当中。新的组件和传入的组件通过props传递信息
const NewComponent=higherOrderComponent(OldComponent)
// 一个简单的高阶组件
import React,{ Component } from 'react';
export default (WrappendComponent)=>{
class NewComponent extends Component {
render() {
return
}
}
return NewComponent;
}
// 给NewComponent做一些数据启动工作
import React,{ Component } from 'react';
export default (WrappendComponent,name)=>{
class NewComponent extends Component {
constructor() {
super();
this.state={
data:null
}
}
componentWillMount() {
let data=localStorage.getItem(name);
this.setState({
data
})
}
render() {
return
}
}
return NewComponent;
}
// 假设上面的代码是在src/wrapWithLoadData.js文件中,我们在别的地方这么用它
import wrapWithLoadData from './wrapWithLoadData';
class InputWithUserName extends Component {
render() {
return
}
}
InputWithUserName=wrapWidthLoadData(InputWithUserName,'username')
export default InputWithUserName
// 别人用这个组件的时候实际是用了被加工过的组件
import InputWithUserName from './InputWithUserName';
class Index extends Component {
render() {
return (
用户名:
)
}
}
context
// 一个组件可以通过getChildContext方法返回一个对象,这个对象就是子树的context,提供context的组件必须提供childContextTypes作为context的声明和验证。
// 一个组件的context只有它的子组件能够访问,它的父组件是不能访问的。
// 就如全局变量一样,context里面的数据能被随意接触就能被随意修改。
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Index extends React.Component {
static childContextTypes={
themeColor:PropTypes.string
}
constructor() {
super();
this.state={
themeColor:'red'
};
}
getChildContext() {
return {
themeColor:this.state.themeColor
};
}
render() {
return (
);
}
}
class Header extends React.Component {
render() {
return (
This is header
)
}
}
class Main extends React.Component {
render() {
return (
This is main
)
}
}
class Title extends React.Component {
static contextTypes={
themeColor: PropTypes.string
}
render() {
return (
这里的标题是红色
)
}
}
class Content extends React.Component {
render() {
return (
这里是内容
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
export default Index
Redux(一):优雅地修改共享状态
// 用create-react-app新建一个项目make-redux
// 修改public/index.html
// 删除src/index.js,添加下面的代码
// 应用的状态
const appState={
title:{
text:'标题',
color:'red'
},
content:{
text:'内容',
color:'blue'
}
}
// 通过渲染函数把上面状态的数据渲染到页面上
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;
}
// 调用
renderApp(appState);
// 问题:
1.所有对共享状态的操作都是不可预料的。
2.“模块(组件)之间需要共享数据”,和“数据可能被任意修改导致不可预料的结果”之间的矛盾。
// 解决方法:
let appState={
title:{
text:'标题',
color:'red'
},
content:{
text:'内容',
color:'blue'
}
}
// 专门负责数据的修改
// 所有对数据的操作必须通过dispatch函数
// 参数action是一个对象,type字段来声明到底想干什么
function dispatch(action) {
switch(action.type) {
case 'UPDATE_TITLE_TEXT':
appState.title.text=action.text;
break;
case 'UPDATE_TITLE_COLOR':
appState.title.color=action.color;
break;
default:
break;
}
}
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;
}
// 首次渲染页面
renderApp(appState);
// 修改标题文本
dispatch({
type:'UPDATE_TITLE_TEXT',
text:'《Helllo World》'
});
// 修改标题颜色
dispatch({
type:'UPDATE_TITLE_COLOR',
color:'blue'
});
// 把新的数据渲染到页面上
renderApp(appState);
Redux(二):抽离store和监控数据变化
抽离出store
let appState={
title:{
text:'标题',
color:'red'
},
content:{
text:'内容',
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;
}
};
function createStore(state,stateChanger) {
const getState=()=>state;
const dispatch=(action)=>stateChanger(state,action);
return {
getState,dispatch
};
};
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;
};
const store=createStore(appState,stateChanger);
renderApp(store.getState());
store.dispatch({
type:'UPDATE_TITLE_TEXT',
text:'《Helllo World》'
});
store.dispatch({
type:'UPDATE_TITLE_COLOR',
color:'blue'
});
renderApp(store.getState());
// 针对每个不同的App,我们可以给createStore传入初始的数据appState,和一个描述数据变化的函数stateChanger,然后生成一个store。需要修改数据的时候通过store.dispatch,需要获取数据的时候通过store.getState。
监控数据变化
let appState={
title:{
text:'标题',
color:'red'
},
content:{
text:'内容',
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;
}
};
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;
};
const store=createStore(appState,stateChanger);
store.subscribe(()=>renderApp(store.getState()));
renderApp(store.getState());
store.dispatch({
type:'UPDATE_TITLE_TEXT',
text:'《Helllo World》'
});
store.dispatch({
type:'UPDATE_TITLE_COLOR',
color:'blue'
});
Redux(三):纯函数
// 一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,我们就把这个函数叫做纯函数。
// 函数的返回结果只依赖于它的参数。
Redux(四):共享结构的对象提高性能
// 解决性能问题:完整代码
let appState={
title:{
text:'标题',
color:'red'
},
content:{
text:'内容',
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;
}
};
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(newAppState,oldAppState={}) {
if(newAppState===oldAppState) {
return;
}
console.log('render app...');
renderTitle(newAppState.title,oldAppState.title);
renderContent(newAppState.content,oldAppState.content);
};
function renderTitle(newTitle,oldTitle={}) {
if(newTitle===oldTitle) {
return;
}
console.log('render title...');
const titleDOM=document.getElementById('title');
titleDOM.innerHTML=newTitle.text;
titleDOM.style.color=newTitle.color;
};
function renderContent(newContent,oldContent={}) {
if(newContent===oldContent) {
return;
}
console.log('render content...');
const contentDOM=document.getElementById('content');
contentDOM.innerHTML=newContent.text;
contentDOM.style.color=newContent.color;
};
const store=createStore(appState,stateChanger);
let oldState=store.getState();
store.subscribe(()=>{
const newState=store.getState();
renderApp(newState,oldState);
oldState=newState;
})
renderApp(store.getState());
store.dispatch({
type:'UPDATE_TITLE_TEXT',
text:'《Helllo World》'
});
store.dispatch({
type:'UPDATE_TITLE_COLOR',
color:'blue'
});
共享结构的对象
// 浅拷贝
const obj={a:1,b:2};
const obj2={...obj};
// 覆盖、拓展对象属性
const obj={a:1,b:2};
const obj2={...obj,b:3,c:4};
优化性能
// 修改stateChanger,让它修改数据的时候,并不会直接修改原来的数据state,而是产生上述的共享结构的对象
function stateChanger(state,action) {
switch(action.type) {
case 'UPDATE_TITLE_TEXT':
// 构建新的对象并且返回
return {
...state,
title:{
...state.title,
text:action.text
}
}
case 'UPDATE_TITLE_COLOR':
// 构建新的对象并且返回
return {
...state,
title:{
...state.title,
color:action.color
}
}
default:
// 没有修改,返回原来的对象
return state;
}
};
// 因为stateChanger不会修改原来对象,而是返回对象,所以需要修改一下createStore。
function createStore(state,stateChanger) {
const listeners=[];
const subscribe=(listener)=>listeners.push(listener);
const getState=()=>state;
const dispatch=(action)=>{
// 覆盖原对象
state=stateChanger(state,action)
listeners.forEach((listener)=>listener())
};
return {
getState,dispatch,subscribe
};
};
Redux(五):不要问为什么的reducer
// 合并appState和stateChanger
function stateChanger(state,action) {
if(!state) {
return {
title:{
text:'标题',
color:'红色'
},
content:{
text:'内容',
color:'蓝色'
}
}
}
switch(action.type) {
case 'UPDATE_TITLE_TEXT':
return {
...state,
title:{
...state.title,
text:action.text
}
}
case 'UPDATE_TITLE_COLOR':
return {
...state,
title:{
...state.title,
color:action.color
}
}
default:
return state;
}
};
Redux(六):Redux总结
// 定义reducer只能是纯函数,功能是负责初始化state,和根据
// state和action计算具有共享结构的新的state
function reducer(state,action) {
// 初始化state和switch case
};
// 生成store
const store=createStore(reducer);
// 监听数据变化重新渲染页面
store.subscribe(()=>renderApp(store.getState()));
// 首次渲染页面
renderApp(store.getState());
// 后面可以随意dispatch了,页面自动更新
store.dispatch(...)
React-redux(一):初始化工程
// 用create-react-app新建一个工程,然后在src/目录下新建三个文件:Header.js、Content.js、ThemeSwitch.js
// 修改Header.js
import React, { Component, PropTypes } from 'react';
class Header extends Component {
render () {
return (
React.js 小书
)
}
}
export default Header
// 修改ThemeSwitch.js
import React, { Component, PropTypes } from 'react';
class ThemeSwitch extends Component {
render () {
return (
)
}
}
export default ThemeSwitch
// 修改Content.js
import React, { Component, PropTypes } from 'react';
import ThemeSwitch from './ThemeSwitch'
class Content extends Component {
render () {
return (
React.js 小书内容
)
}
}
export default Content
// 修改index.js
import React, { Component, PropTypes } from 'react'
import ReactDOM from 'react-dom'
import Header from './Header'
import Content from './Content'
import './index.css'
class Index extends Component {
render () {
return (
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
React-redux(二):结合context和store
// index.js
import React,{ Component,PropTypes } from 'react';
import ReactDOM from 'react-dom';
import Header from './Header';
import Content from './Content';
import './index.css';
function createStore(reducer) {
let state=null;
const listeners=[];
const subscribe=(listener)=>listeners.push(listener);
const getState=()=>state;
const dispatch=(action)=>{
state=reducer(state,action)
listeners.forEach((listener)=>listener())
}
dispatch({});
return {
getState,dispatch,subscribe
}
}
const themeReducer=(state,action)=>{
if(!state) {
return {
themeColor:'red'
}
}
switch(action.type) {
case 'CHANGE_COLOR':
return {
...state,themeColor:action.themeColor
}
default:
return state
}
}
const store=createStore(themeReducer)
class Index extends React.Component {
static childContextTypes={
store:PropTypes.object
}
getChildContext() {
return {
store
}
}
render () {
return (
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
// Header.js
import React,{ Component,PropTypes } from 'react';
class Header extends React.Component {
static contextTypes={
store:PropTypes.object
}
constructor() {
super();
this.state={
themeColor:''
}
}
componentWillMount() {
this._updateThemeColor()
}
_updateThemeColor() {
const {store}=this.context;
const state=store.getState();
this.setState({
themeColor:state.themeColor
})
}
render() {
return (
标题
)
}
}
export default Header
// Content.js
import React,{ Component,PropTypes } from 'react';
import ThemeSwitch from './ThemeSwitch';
class Content extends React.Component {
static contextTypes={
store:PropTypes.object
}
constructor() {
super();
this.state={
themeColor:''
}
}
componentWillMount() {
this._updateThemeColor()
}
_updateThemeColor() {
const { store }=this.context;
const state=store.getState();
this.setState({ themeColor: state.themeColor })
}
render() {
return (
内容
)
}
}
export default Content