React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”。
在安装 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/ 即可看到效果
例如:
<html>
<head>
<meta charset="UTF-8" />
<title>Hello Reacttitle>
<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 来替代常规的 JavaScript,例如代码const element =
就是 JSX 语言,像是传统 js 与 html 的结合,JSX 的作用就是声明 React 当中的元素,例如用变量 element 指向声明的元素,之后要使用该元素的话,直接用 element 就好了。Hello, world!
;
JSX 是 JavaScript 的语法扩展。 推荐在 React 中使用 JSX 来描述用户界面。
JSX 是在 JavaScript 内部实现的。
JSX 定义的元素是普通的对象,React DOM 可以确保浏览器 DOM 的数据内容与 React 元素保持一致。
注意:由于 JSX 就是 JavaScript,一些标识符像 class
和 for
不建议作为属性名。作为替代,React DOM 使用 className
和 htmlFor
来做对应的属性,这是因为 class
和 for
是 JavaScript 的保留字。
const element = (
<div>
<h1>Hello</h1>
<h2>Good to see you</h2>
</div>
);
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>
Reate 的组件相当于 js 中的函数
prarm
参数,返回一个元素。这类组件被称为“函数组件”,因为它本质上就是 js函数。function ReturnMessage(prarm) {
return <h3>hello, {
prarm.name}</h3>;
}
class Welcome extends React.Component {
render() {
return <h1>Hello, {
this.props.name}</h1>;
}
}
React 元素也可以是用户自定义的组件:const element3 =
,看以下代码块:
<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 是组件对象最重要的属性,值是对象(可以包含多个数据)
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件),state 的值一旦被改变会触发该组件的重新渲染
constructor (props) {
super(props)
this.state = {
stateProp1 : value1,
stateProp2 : value2
}
}
读取某个状态值:this.state.状态值名
更新状态
this.setState({
stateProp1 : value1,
stateProp2 : value2
})
<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(properties的简写) 属性
组件标签的所有属性都保存在 props 中
通过标签属性从组件外向组件内传递变化的数据
注意: 组件内部不要修改 props 数据,props 具有只读性,组件无论是使用函数声明或是 class 声明,都决不能修改自身的 props。例如以下这个 sum
函数:
function sum(a, b) {
return a + b;
}
这样的函数被称为纯函数,因为该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。所有组件都必须像纯函数一样保护它们的 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
//该方式写在组件类的外面
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
}
Person.defaultProps = {
age: 20,
gender: "男"
};
constructor (props) {
super(props)
console.log(props) // 查看所有属性
}
const p1 = {
name: "tom",
age: 10,
gender: "女"
};
// ReactDOM.render( ,
// document.getElementById("example"));
ReactDOM.render(<PersonClass {
...p1} />, document.getElementById("example"));
组件内的标签都可以定义 ref 属性来标识自己,例如: this.msgInput = input}/>
在组件中可以通过this.msgInput
来得到对应的真实DOM元素
可通过 ref 获取组件内容特定标签对象,进行读取其相关数据
新建一个 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>
通过元素的 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 指 JS 对象,也就是一个组件中 render() 方法中的 html 元素,通过ReactDOM.render()
方法将虚拟 DOM 转化为真实的 DOM
DOM Diff 算法:
React 本身只关注于界面, 并不包含发送 Ajax 请求的代码
前端应用需要通过 Ajax 请求与后台进行交互(json数据)
React 应用中需要集成第三方 Ajax 库(或自己封装)
jQuery: 比较重,如果需要另外引入不建议使用
axios: 轻量级,建议使用
封装 XmlHttpRequest 对象的 Ajax
promise 风格
可以用在浏览器端和 node 服务器端
不再使用 XmlHttpRequest 对象提交 Ajax 请求
为了兼容低版本的浏览器,可以引入兼容库 fetch.js
这里使用 axios 来获取远程数据
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 是 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)
});
发布(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 路由组件,是一个专门的插件库,带有自己的组件标签;专门用来实现一个 SPA 应用
(single page web application)单页的 web 应用,React 中是组件套组件的开发方式,例如点击 nav 导航栏的链接,在浏览器中看似是在跳页面,其实只是改变了组件,页面并没有改变。
一个应用只有一个完整页面
点击页面按钮或链接不会刷新页面,当然也不会向服务器发请求
点击链接渲染另一个组件,做页面的局部刷新
数据都通过 Ajax 异步获取
一个路由就是一个键值对映射关系,key 就好比是对应组件的 url,value 就是即将渲染的组件名
前台路由例如:
,当浏览器的 hash 变为 #myComponent 时, 当前路由组件就会变为 MyComponent 组件
后台路由例如:router.get(path, function(req, res))
,当 node 接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
网址: https://github.com/ReactTraining/history
管理浏览器会话历史(history)的工具库
包装的是原生 DOM 中 window.history 和 window.location.hash 对象
History.createBrowserHistory(): 得到封装 window.history 的管理对象
History.createHashHistory(): 得到封装 window.location.hash 的管理对象
history.push(): 添加一个新的历史记录
history.replace(): 用一个新的历史记录替换当前的记录
history.goBack(): 回退到上一个历史记录
history.goForword(): 前进到下一个历史记录
history.listen(function(location){}): 监视历史记录的变化
先安装 router 组件:在项目目录命令行下npm install --save react-router
引入
组件:import {NavLink} from 'react-router-dom'
在项目入口处的 index.js 中要引用
:
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")
);
组件是一个基本的导航链接组件,使用例如:
<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>
的 to 属性与
的 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>
)
}
组件相当于默认的路由,当 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>
)
}
}
将 messages 数组中的 id 的赋予链接,
中使用 :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是一个独立专门用于做状态管理的JS库(不属于 react 插件库)
它可以用在 react, angular, vue 等项目中, 但基本与 react 配合使用
作用:集中式管理 react 应用中多个组件共享的状态(变量),可以说 redux 就是将多个组件共享的变量封装到一个 js 中,对变量增删改查的方法也封装到 js 中,解决组件间各种传参而导致关系复杂冗余的问题
先了解 redux的三个核心概念:action、store、reducer
//对 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;
}
}
//增加
export const INCREMENT = (number) => ({
type: 'add', data: number })
//减少
export const DECREMENT = (number) => ({
type: 'cut', data: number })
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 方法。
总体原则: 能不用就不用,如果不用比较吃力才考虑使用
某个组件的状态,需要共享
某个状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态
npm install --save redux
安装 redux
//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 插件将所有组件分为制作 UI 的组件类与管理数据和业务的 redux 类。
只负责 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 与组件连接在一起。