文章目录
React 是什么
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
安装 React
在安装 React 之前需要安装 nodejs,之后使用 node 中的 npm 命令来搭建 Reate 运行环境
安装步骤:
-
安装 nodejs
-
安装淘宝定制的 cnpm 命令,使用 cnpm 代替 npm,速度会快很多:
$ npm install -g cnpm --registry=https://registry.npm.taobao.org
$ npm config set registry https://registry.npm.taobao.org
使用 $ cnpm install [name] 来安装你想要的模块 -
使用 create-react-app 快速构建 React 开发环境:
$ cnpm install -g create-react-app
$ create-react-app my-app
创建 create 项目
$ cd my-app/
$ npm start
访问 http://localhost:3000/ 即可看到效果
React 中的元素渲染
例如:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React</title>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
const element = <h1>Hello, world!</h1>;
ReactDOM.render(
element,
document.getElementById('example')
);
</script>
</body>
</html>
React 通过方法ReactDOM.render()
将元素 element 渲染到 id 为 example 的标签内,当然也可同时定义多个 div 进行多次渲染,例如:
<div id="example" style="width: 100px; height: 100px; background-color: antiquewhite;"></div>
<div id="example2" style="width: 100px; height: 100px; background-color:aqua"></div>
<div id="example3" style="width: 100px; height: 100px; background-color:aquamarine"></div>
<script type="text/babel">
const element = <h1>Hello, world!</h1>;
const element2 = <h2>Hello, world!</h2>;
const element3 = <h3>Hello, world!</h3>;
ReactDOM.render(
element,
document.getElementById('example')
);
ReactDOM.render(
element2,
document.getElementById('example2')
);
ReactDOM.render(
element3,
document.getElementById('example3')
);
</script>
React JSX
在 React 中使用 JSX 来替代常规的 JavaScript,例如代码const element = <h1>Hello, world!</h1>;
就是 JSX 语言,像是传统 js 与 html 的结合,JSX 的作用就是声明 React 当中的元素,例如用变量 element 指向声明的元素,之后要使用该元素的话,直接用 element 就好了。
-
JSX 是 JavaScript 的语法扩展。 推荐在 React 中使用 JSX 来描述用户界面。
-
JSX 是在 JavaScript 内部实现的。
-
JSX 定义的元素是普通的对象,React DOM 可以确保浏览器 DOM 的数据内容与 React 元素保持一致。
使用 JSX 的好处:
- JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。
- 它是类型安全的,在编译过程中就能发现错误。
- 使用 JSX 编写模板更加简单快速。
注意:由于 JSX 就是 JavaScript,一些标识符像 class
和 for
不建议作为属性名。作为替代,React DOM 使用 className
和 htmlFor
来做对应的属性,这是因为 class
和 for
是 JavaScript 的保留字。
使用 React JSX
- JSX 中可定义多个元素,例如:
const element = (
<div>
<h1>Hello</h1>
<h2>Good to see you</h2>
</div>
);
- 在 JSX 中嵌入 {} 表达式,可在大括号 {} 内放置任何有效的 js 表达式,例如算式、
user.userName
等等
<body>
<div id="example"></div>
<script type="text/babel">
const user = { userName: "张三", age: "18" };
const element = <h1>Hello, {user.userName}</h1>;
ReactDOM.render(
element,
document.getElementById('example')
);
</script>
</body>
效果为:
React 组件
Reate 的组件相当于 js 中的函数
- 定义方式1:看下面代码块,React 定义了一个组件,该组件接收一个
prarm
参数,返回一个元素。这类组件被称为“函数组件”,因为它本质上就是 js函数。
function ReturnMessage(prarm) {
return <h3>hello, {prarm.name}</h3>;
}
- 定义方式2:下面代码块使用 ES6 的 class 来定义组件,使用 class 来定义组件会有额外特性,之后介绍。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染组件
React 元素也可以是用户自定义的组件:const element3 = <ReturnMessage name="张三" />;
,看以下代码块:
<body>
<div id="example3"></div>
<script type="text/babel">
function ReturnMessage(props) {
return <h3>hello, {props.name}</h3>;
}
const element3 = <ReturnMessage name="张三" />;
ReactDOM.render(
element3,
document.getElementById('example3')
);
</script>
</body>
当 React 元素(JSX)为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。这段代码会在页面上渲染 hello, 张三
。
注意,原生 HTML 元素名以小写字母开头,而自定义的 React 类名以大写字母开头,比如 ReturnMessage不能写成 returnMessage。除此之外还需要注意组件类只能包含一个顶层标签,否则也会报错。
复合组件
看以下代码,复合组件就像层层方法调用,在 App() 中调用了 Name() 与 Age() 组件,在 ReactDOM.render() 中调用 App() 组件。
<body>
<div id="example2" style="width: 100px; height: 100px; background-color:aqua"></div>
<script type="text/babel">
const element2 = <h2>Hello, world!</h2>;
ReactDOM.render(
<App />,
document.getElementById('example2')
);
function App() {
return (
<div>
<Name name="张三" />
<Age age="18" />
</div>
);
}
function Name(props) {
return <h6>名称:{props.name}</h6>;
}
function Age(props) {
return <h6>年龄:{props.age}</h6>;
}
</script>
</body>
效果:
组件的三大属性(state、props、refs)
state
-
state 是组件对象最重要的属性,值是对象(可以包含多个数据)
-
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件),state 的值一旦被改变会触发该组件的重新渲染
使用 state
- 初始化状态:
constructor (props) {
super(props)
this.state = {
stateProp1 : value1,
stateProp2 : value2
}
}
-
读取某个状态值:
this.state.状态值名
-
更新状态
this.setState({
stateProp1 : value1,
stateProp2 : value2
})
使用 state 的小案例
点击按钮,左边的数字 + 1:
<body>
<div id="example2"></div>
//点击按钮,数字+1
class MyComponent2 extends React.Component {
constructor(props) {
super(props);
this.click = this.click.bind(this);
//初始化 state
this.state = { count: 0 }
}
click(e) {
var i = this.state.count + 1;
//修改 state
this.setState({
count: i
})
}
render() {
return (
<div>
<span>{this.state.count}</span>
<button type="button" onClick={this.click}>+1按钮</button>
</div>
)
}
}
ReactDOM.render(<MyComponent2 />, document.getElementById("example2"));
</script>
</body>
props
-
每个组件对象都会有 props(properties的简写) 属性
-
组件标签的所有属性都保存在 props 中
作用
-
通过标签属性从组件外向组件内传递变化的数据
-
注意: 组件内部不要修改 props 数据,props 具有只读性,组件无论是使用函数声明或是 class 声明,都决不能修改自身的 props。例如以下这个
sum
函数:
function sum(a, b) {
return a + b;
}
这样的函数被称为纯函数,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。所有组件都必须像纯函数一样保护它们的 props 不被更改。
使用 props
首先构建一个组件 Person:
function Person(props) {
return (
<ul>
<li>name:{props.name}</li>
<li>age:{props.age}</li>
<li>gender:{props.gender}</li>
</ul>
)
}
要使用ReactDOM.render()
将一个 person 对象传入 Person 组件中,返回相应元素;age、gender 属性要设置默认,name、age 属性的类型要被限制,以下是使用方式:
- 内部读取某个属性值
this.props.propertyName
- 对 props 中的属性值进行类型限制和必要性限制:
//该方式写在组件类的外面
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
//写在组件类里面的形式,static 表示给组件类指定属性
static propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
- 对 props 设置默认属性值:
Person.defaultProps = {
age: 20,
gender: "男"
};
- 组件类的构造函数:
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
- 扩展属性,将对象的所有属性通过props传递:
const p1 = {
name: "tom",
age: 10,
gender: "女"
};
// ReactDOM.render(<Person name={p1.name} age={p1.age} />,
// document.getElementById("example"));
ReactDOM.render(<PersonClass {...p1} />, document.getElementById("example"));
效果:
refs
-
组件内的标签都可以定义 ref 属性来标识自己,例如:
<input type="text" ref={input => this.msgInput = input}/>
-
在组件中可以通过
this.msgInput
来得到对应的真实DOM元素 -
可通过 ref 获取组件内容特定标签对象,进行读取其相关数据
refs 使用的小案例
新建一个 click 事件,在点击按钮时弹出文本框中的内容:
<div id="example"></div>
<script type="text/babel">
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.click = this.click.bind(this);
}
click(e) {
//e.target 得到发生事件的DOM元素对象
//alert(e.target);
alert(this.refs.first.value);
}
render() {
return (
<div>
<input type="text" ref="first" />
<button type="button" onClick={this.click}>按钮</button>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.getElementById("example"));
</script>
注意:
- 组件内置的方法中的 this 为组件对象
- 在组件类中自定义的方法中 this 为 null
- 强制绑定 this 通过函数对象的 bind()
- 箭头函数(ES6模块化编码时才能使用)
组件内的事件
通过元素的 onXXX 属性指定一个事件处理函数,例如:onClick={this.click}
,在组件中定义一个 click 函数。
事件函数的使用
//这种事件函数需要在组件构造当中进行绑定,例如 this.click = this.click.bind(this);
click(event) {
//操作...
}
//下列定义事件函数的方法不需在构造中 bind()
click2 = (event) => {
//操作...
}
//如果子组件要调用父组件函数的话,父组件需要将函数传给自组件,例如:
//这是父组件的 operate() 与 render() 函数
operate = (`可以定义参数,也可以空着`) => {
//操作...
}
render() {
return (
<div>
<Son operate={this.operate} />
</div>
)
}
//子组件在调用父组件的函数前,需要规定参数类型,例如:
static propTypes = {
operate: PropTypes.func.isRequired
}
//子组件调用父组件函数
click = () => {
//从props中获取事件函数
const {operate} = this.props;
operate(`定义了参数就传入参数`);
}
render() {
return (
<div>
<a onClick={this.click}>操作</a>
</div>
)
}
组件的生命周期
-
组件对象从创建到死亡它会经历特定的生命周期阶段
-
React组件对象包含一系列的勾子函数(生命周期回调函数),在生命周期特定时刻回调
-
在定义组件时可以重写特定的生命周期回调函数,让不同的函数做特定的工作
组件三个生命周期状态
-
Mount:将组件插入真实 DOM
-
Update:组件被重新渲染
-
Unmount:组件被移出真实 DOM
生命周期方法与流程
- 第一次初始化渲染显示:
ReactDOM.render()
-
constructor()
: 创建对象初始化构造器,初始化 state -
componentWillMount()
: 组件将被插入 DOM 时触发 -
render()
: 组件插入虚拟 DOM 时触发,也就是开始渲染 -
componentDidMount()
: 组件已经插入到 DOM 后触发
- 每次更新状态时
state: this.setSate()
-
componentWillUpdate()
: 组件在更新数据前触发 -
render()
: 更新(重新渲染) -
componentDidUpdate()
: 组件更新后触发
- 移除组件:
ReactDOM.unmountComponentAtNode(containerDom)
componentWillUnmount()
: 组件将要从 DOM 中移除触发
重要的生命周期方法
-
render()
: 初始化渲染或更新渲染触发 -
componentDidMount()
: 开启监听, 发送 ajax 请求 -
componentWillUnmount()
: 做一些收尾工作, 如: 清理定时器 -
componentWillReceiveProps()
:
虚拟DOM 与 DOM Diff 算法
虚拟 DOM 指 JS 对象,也就是一个组件中 render() 方法中的 html 元素,通过ReactDOM.render()
方法将虚拟 DOM 转化为真实的 DOM
DOM Diff 算法:
- 当页面初始化完成,所有组件都渲染好之后
- 在后续操作中,某一个组件的 state 被改变
- React 重新创建虚拟 DOM 树
- React 比较旧 DOM 与 新 DOM 的区别
- 如果有区别,则重新渲染该组件,也就是重新调用 render() 方法
React Ajax
说明
-
React 本身只关注于界面, 并不包含发送 Ajax 请求的代码
-
前端应用需要通过 Ajax 请求与后台进行交互(json数据)
-
React 应用中需要集成第三方 Ajax 库(或自己封装)
常用的 Ajax 请求库
-
jQuery: 比较重,如果需要另外引入不建议使用
-
axios: 轻量级,建议使用
-
封装 XmlHttpRequest 对象的 Ajax
-
promise 风格
-
可以用在浏览器端和 node 服务器端
- fetch: 原生函数,但老版本浏览器不支持
-
不再使用 XmlHttpRequest 对象提交 Ajax 请求
-
为了兼容低版本的浏览器,可以引入兼容库 fetch.js
在 Reate 中使用 Ajax
这里使用 axios 来获取远程数据
- 引入 axios
<script src="https://cdn.bootcdn.net/ajax/libs/axios/0.20.0-0/axios.js" type="text/javascript"></script>
- 在 React 中使用
componentDidMount() {
const url = "localhost:8080/api/item";
axios.get(url).then(response => {
const { name, age } = response.data.items;
this.setState({ name: name, age: age });
}).catch(error => {
//异常获取
console.log(error.message);
});
}
Fetch
Fetch 是 React 原生的异步请求工具,使用:
componentDidMount() {
const url = "localhost:8080/api/item";
//默认是 get 请求
fetch(url).then(response => {
return response.json();
}).then(data => {
const { full_name, html_url } = data.items[0];
this.setState({ repoName: full_name, repoUrl: html_url });
}).catch(error => {
//异常获取
console.log(error.message);
});
}
Fetch 的 get 请求:
fetch(url).then(response => {
return response.json();
}).then(data => {
console.log(data)
}).catch(e => {
console.log(e)
});
Fetch 的 post 请求:
fetch(url, {
method: "POST",
//要发送的数据
body: JSON.stringify(data),
}).then(data => {
console.log(data)
}).catch(e => {
console.log(e)
});
组件间的通信
- 组件间常用的通信可以使用 props,例如上文那样传递数值或方法;但 props 传递只能一层一层传递,比如组件C要调用组件A的方法,而A中渲染B,B中再渲染C,方法就只能从 A->B->C 。
- 使用消息订阅(subscribe)、发布(publish)机制
- redux,后续说明
消息订阅发布机制
发布(publish)消息就像是去调用其他组件内的方法,将参数主动传递过去,而订阅(subscribe)像是定义了一个方法,等待别人来调用我。
使用
-
需要使用到 PubSubJS 库,在项目下
npm install pubsub-js --save
安装 -
在项目中引入
import PubSub from 'pubsub-js'
-
发布消息:
//public('方法名',参数);
PubSub.public('search', searchName);
- 订阅消息:
//subscribe('方法名',回调函数);
PubSub.subscribe('search', (msg, searchName) => {
console.log(msg);
//操作...
});
React-router
介绍
React-router 就是 React 路由组件,是一个专门的插件库,带有自己的组件标签;专门用来实现一个 SPA 应用
SPA 应用的理解
-
(single page web application)单页的 web 应用,React 中是组件套组件的开发方式,例如点击 nav 导航栏的链接,在浏览器中看似是在跳页面,其实只是改变了组件,页面并没有改变。
-
一个应用只有一个完整页面
-
点击页面按钮或链接不会刷新页面,当然也不会向服务器发请求
-
点击链接渲染另一个组件,做页面的局部刷新
-
数据都通过 Ajax 异步获取
对路由的理解
路由是什么?
一个路由就是一个键值对映射关系,key 就好比是对应组件的 url,value 就是即将渲染的组件名
路由分类(前台路由、后台路由)
前台路由例如:<Route path="/myComponent" component={MyComponent}>
,当浏览器的 hash 变为 #myComponent 时, 当前路由组件就会变为 MyComponent 组件
后台路由例如:router.get(path, function(req, res))
,当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
前台路由实现
- history库
-
网址: https://github.com/ReactTraining/history
-
管理浏览器会话历史(history)的工具库
-
包装的是原生 DOM 中 window.history 和 window.location.hash 对象
- history API
-
History.createBrowserHistory(): 得到封装 window.history 的管理对象
-
History.createHashHistory(): 得到封装 window.location.hash 的管理对象
-
history.push(): 添加一个新的历史记录
-
history.replace(): 用一个新的历史记录替换当前的记录
-
history.goBack(): 回退到上一个历史记录
-
history.goForword(): 前进到下一个历史记录
-
history.listen(function(location){}): 监视历史记录的变化
React-router 相关 API
组件
-
<BrowserRouter>
-
<HashRouter>
-
<Route>
-
<Redirect>
-
<Link>
-
<NavLink>
-
<Switch>
路由基本使用
先安装 router 组件:在项目目录命令行下npm install --save react-router
引入<NavLink>
组件:import {NavLink} from 'react-router-dom'
在项目入口处的 index.js 中要引用<BrowserRouter>
:
import React from 'react'
import { render } from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './components/App'
render(
<BrowserRouter>
<App />
</BrowserRouter>
, document.getElementById("root")
);
<NavLink>
组件是一个基本的导航链接组件,使用例如:
<div className="list-group">
<NavLink className="list-group-item" to='/about' >About</NavLink>
<NavLink className="list-group-item" to='/home'>Home</NavLink>
</div>
下面是前台路由组件:
<div className="panel-body">
<Switch>
<Route path='/about' component={About} />
<Route path='/home' component={Home} />
<Redirect to='/about' />
</Switch>
</div>
<NavLink>
的 to 属性与<Route>
的 path 属性一致,component 属性对应要渲染的控件。
嵌套路由的使用
嵌套路由就是在一级路由下的组件中,再定义其他路由。在上文的案例中的 Home 组件中再定义路由:
export default function Home() {
return (
<div>
<h2>Home组件内容</h2>
<div>
<ul className='nav nav-tabs'>
<li>
<NavLink to='/home/news'>News</NavLink>
</li>
<li>
<NavLink to='/home/message'>Messages</NavLink>
</li>
</ul>
</div>
<div className="panel-body">
<Switch>
<Route path='/home/news' component={News} />
<Route path='/home/message' component={Messages} />
<Redirect to='/home/news' />
</Switch>
</div>
</div>
)
}
<Redirect>
组件相当于默认的路由,当 Home 组件渲染完成时,自动渲染 News 组件。
向路由组件中传递参数
延续上文案例,Messages 组件中包含三个链接,点击其中一个链接并传入不同 id,在 Messages 组件的下方显示对应 id 的信息,先看看 Messages 组件:
import React, { Component } from 'react'
import { Route, NavLink } from 'react-router-dom'
import Message_detai from './Message_detai'
export default class Mssages extends Component {
state = { messages: [] }
componentDidMount() {
let messages = [
{ id: 1, title: 'message001' },
{ id: 3, title: 'message003' },
{ id: 5, title: 'message005' },
];
this.setState({ messages });
}
render() {
return (
<div>
<ul>
{
this.state.messages.map((element, index) => {
return <li key={index}><NavLink to={`/home/message/messagedetail/${element.id}`}>{element.title}</NavLink></li>
})
}
</ul>
<Route path='/home/message/messagedetail/:id' component={Message_detai} />
</div>
)
}
}
<NavLink>
将 messages 数组中的 id 的赋予链接,<Router>
中使用 :id 表示所传的参数,格式也就是:参数名
。再看 Message_detai:
import React from 'react'
const messageDetails = [
{ id: 1, title: 'Message001', content: 'content1' },
{ id: 2, title: 'Message002', content: 'content2' },
{ id: 3, title: 'Message003', content: 'content3' },
]
export default function Message_detai(props) {
const { id } = props.match.params;
const msg = messageDetails.find((msg) => msg.id === id * 1);
return (
<ul>
<li>id:{msg.id}</li>
<li>title:{msg.title}</li>
<li>content:{msg.content}</li>
</ul>
)
}
const { id } = props.match.params;
是从 props 中获取名为 id 的参数,messageDetails.find( function(){} )
是遍历数组,直到方法返回 true 时返回此刻遍历到的对象,(msg) => msg.id === id * 1
,msg 就是数组中的对象,当两个 id 相等时返回 true,因为从 props 中获取的 id 值类型是字符串,所有要*1变成数字。
Redux
Redux 是什么
-
redux是一个独立专门用于做状态管理的JS库(不属于 react 插件库)
-
它可以用在 react, angular, vue 等项目中, 但基本与 react 配合使用
-
作用:集中式管理 react 应用中多个组件共享的状态(变量),可以说 redux 就是将多个组件共享的变量封装到一个 js 中,对变量增删改查的方法也封装到 js 中,解决组件间各种传参而导致关系复杂冗余的问题
Redux 工作流程
先了解 redux的三个核心概念:action、store、reducer
- reducer 中封装了对变量的操作方法,传入方法的参数为 state 与 action,state 不是react 中的状态,只是一个变量;action 是一个对象,表明要对 state 做什么操作;使用例如:
//对 state 进行增减的方法
// state = 0 表明state的初始值为0
export function count(state = 0, action) {
switch (action.type) {
case 'add':
return state + action.data;
case 'cut':
return state - action.data;
default:
return state;
}
}
- action 就是传入 reducer 方法中的对象,包含 type、data 属性,type 值为字符串,类似于方法名,具有唯一性,data 就是数据,值类型任意;type 为必要参数,data 为可选参数。使用例如:
//增加
export const INCREMENT = (number) => ({ type: 'add', data: number })
//减少
export const DECREMENT = (number) => ({ type: 'cut', data: number })
- store 是将 state,action,reducer 联系在一起的对象。react 组件调用
store.getState()
获取 state 值,调用store.dispatch(action对象)
方法去修改 state,调用store.subscribe(渲染组件)
去重新渲染组件。使用例如:
this.props.store.dispatch(actions.INCREMENT(count));
工作流程:
react 组件首先要拿到一个 action 对象,可以在一个 js 中定义很多 action 对象,再去获取 store 对象,store 对象可由父组件通过 props 传给子组件,调用 store 的 dispatch 方法传入 action,然后进入 reducer 的方法中,reducer 判断 action 的 type ,对 state 进行操作,返回一个新得 state 给 store,state 一旦改变,则会触发 store 的 subscribe 方法。
什么情况下需要使用 redux
-
总体原则: 能不用就不用,如果不用比较吃力才考虑使用
-
某个组件的状态,需要共享
-
某个状态需要在任何地方都可以拿到
-
一个组件需要改变全局状态
-
一个组件需要改变另一个组件的状态
对 state 进行增减的案例代码:
npm install --save redux
安装 redux
项目结构:
先创建 store.js :
//store.js
import { counter } from './reducers'
import { createStore } from 'redux'
//创建一个 store 对象,传入 reducer 中的方法
const store = createStore(counter);
export default store
inedx.js :
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';
import store from './redux/store'
function render() {
ReactDOM.render(<App store={store} />, document.getElementById('root'));
}
//初始化渲染
render();
//订阅监听,store中状态变化触发该函数
store.subscribe(render);
actios.js :
//存放 action 对象的 js
import { increment, decrement } from './action_types'
//增加
export const INCREMENT = (number) => ({ type: increment, data: number })
//减少
export const DECREMENT = (number) => ({ type: decrement, data: number })
action_types.js :
//action 对象的type类型
export const increment = 'increment'
export const decrement = 'decrement'
reducers.js :
/**
* 包含n个reducer的模块
*/
import { increment, decrement } from './action_types'
export function counter(state = 0, action) {
switch (action.type) {
case increment:
return state + action.data;
case decrement:
return state - action.data;
default:
return state;
}
}
主组件 App.jsx :
import React from 'react'
import * as actions from '../redux/actions'
export default class App extends React.Component {
add = () => {
const count = this.select.value * 1;
this.props.store.dispatch(actions.INCREMENT(count));
}
cut = () => {
const count = this.select.value * 1;
this.props.store.dispatch(actions.DECREMENT(count));
}
addIfOdd = () => {
if (this.props.store.getState() % 2 === 1) {
const count = this.select.value * 1;
this.props.store.dispatch(actions.INCREMENT(count));
}
}
render() {
const count = this.props.store.getState();
return (
<div>
<p>click {count} times</p>
<div>
<select ref={select => this.select = select}>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button onClick={this.add}>+</button>
<button onClick={this.cut}>-</button>
<button onClick={this.addIfOdd}>add if 奇数</button>
</div>
</div>
)
}
}
react-redux
之前的 react 组件中还存在着 redux 代码,可使用 react-redux 插件将所有组件分为制作 UI 的组件类与管理数据和业务的 redux 类。
- UI组件
-
只负责 UI 的呈现,不带有任何业务逻辑
-
通过 props 接收数据(一般数据和函数)
-
不使用任何 Redux 的 API
-
一般保存在 components 文件夹下
- 容器组件
-
负责管理数据和业务逻辑,不负责UI的呈现
-
使用 Redux 的 API
-
一般保存在 containers 文件夹下
将之前的对 state 操作的代码进行修改
首先安装npm install --save react-redux
将 App.jsx 中的 redux 代码整合到另一个 js 中,将 App.jsx 更名为 Counter.jsx:
/*
UI组件: 不包含任何redux API
*/
import React from 'react'
import PropTypes from 'prop-types'
export default class Counter extends React.Component {
//使用 props 接受参数
static propTypes = {
count: PropTypes.number.isRequired,
increment: PropTypes.func.isRequired,
decrement: PropTypes.func.isRequired
}
add = () => {
const count = this.select.value * 1;
this.props.increment(count);
}
cut = () => {
const count = this.select.value * 1;
this.props.decrement(count);
}
addIfOdd = () => {
if (this.props.count % 2 === 1) {
const count = this.select.value * 1;
this.props.increment(count);
}
}
render() {
const { count } = this.props;
return (
<div>
<p>click {count} times</p>
<div>
<select ref={select => this.select = select}>
<option>1</option>
<option>2</option>
<option>3</option>
</select>
<button onClick={this.add}>+</button>
<button onClick={this.cut}>-</button>
<button onClick={this.addIfOdd}>add if 奇数</button>
</div>
</div>
)
}
}
当然被抽取出来的 redux 代码给 Counter.jsx 传值,其被封装为 App.js:
import React from 'react'
import { connect } from 'react-redux'
import { INCREMENT, DECREMENT } from '../redux/actions'
import Counter from '../components/Counter'
//将redux与react联系起来,为props属性赋值
//该方法用于包装 UI 组件生成容器组件,将参数赋予 Counter
export default connect(
state => ({ count: state }),
{ increment: INCREMENT, decrement: DECREMENT }
)(Counter)
index.js 中 :
import React from 'react';
import ReactDOM from 'react-dom';
import App from './container/App';
import store from './redux/store'
import { Provider } from 'react-redux'
ReactDOM.render(
//Provider让所有组件都可以得到state数据,并维护store
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
代码结构:
最早 react 组件是获取 action 对象,再获取 store 对象,拿 store 去调用方法,现在优化后组件与 redux 隔离开来,直接调用从外部传来的方法。connect 将 store 与组件连接在一起。