1. 几个重要概念理解
模块与组件
模块:
- 理解: 向外提供特定(局部)功能的js程序, 一般就是一个js文件
- 为什么: js代码更多更复杂
- 作用: 复用js, 简化js的编写, 提高js运行效率
组件:
- 理解: 用来实现特定功能效果的代码集合(html/css/js)
- 为什么: 一个界面的功能更复杂
- 作用: 复用编码, 简化项目编码, 提高运行效率
模块化与组件化
模块化:
- 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用
组件化:
- 当应用是以多组件的方式实现功能, 这上应用就是一个组件化的应用
2. React的基本认识
- Facebook开源的一个js库
- 一个用来动态构建用户界面的js库
React的特点
- Declarative(声明式编码)
- Component-Based(组件化编码)
- Learn Once, Write Anywhere(支持客户端与服务器渲染)
- 高效
- 单向数据流
React高效的原因
- 虚拟(virtual)DOM, 不总是直接操作DOM(批量更新, 减少更新的次数)
- 高效的DOM Diff算法, 最小化页面重绘(减小页面更新的区域)
3. 使用React
* 导入相关js库文件(react.js, react-dom.js, babel.min.js)
* 编码:
```
<div id="container"></div>
<script type="text/babel">
var aa = 123
ReactDOM.render(<h1>{aa}</h1>, containerDOM);
</script>
```
4. JSX
- 全称: JavaScript XML
react定义的一种类似于XML的JS扩展语法: XML+JS
作用: 用来创建react虚拟DOM(元素)对象
- js中直接可以套标签, 但标签要套js需要放在{}中
- 在解析显示js数组时, 会自动遍历显示
把数据的数组转换为标签的数组:
var liArr = dataArr.map(function(item, index){ return <li key={index}>{item}</li> })
注意:
- 标签必须有结束
- 标签的class属性必须改为className属性
- 标签的style属性值必须为: {{color:’red’, width:12}}
5. Component : React是面向组件编程的(组件化编码开发)
* 基本理解和使用
* 自定义的标签: 组件类(函数)/标签
* 创建组件类
```
//方式1: 无状态函数(最简洁, 推荐使用)
function MyComponent1() {
return <h1>自定义组件标题11111</h1>;
}
//方式2: ES6类语法(复杂组件, 推荐使用)
class MyComponent3 extends React.Component {
render () {
return <h1>自定义组件标题33333</h1>;
}
}
//方式3: ES5老语法(不推荐使用了)
var MyComponent2 = React.createClass({
render () {
return <h1>自定义组件标题22222</h1>;
}
});
```
* 渲染组件标签
```
ReactDOM.render(<MyComp />, cotainerEle);
```
* ReactDOM.render()渲染组件标签的基本流程
* React内部会创建组件实例对象/调用组件函数, 得到虚拟DOM对象
* 将虚拟DOM并解析为真实DOM
* 插入到指定的页面元素内部
* props
* 所有组件标签的属性的集合对象
* 给标签指定属性, 保存外部数据(可能是一个function)
* 在组件内部读取属性: this.props.propertyName
* 作用: 从目标组件外部向组件内部传递数据
* 对props中的属性值进行类型限制和必要性限制
```
Person.propTypes = {
name: React.PropTypes.string.isRequired,
age: React.PropTypes.number.isRequired
}
```
* 扩展属性: 将对象的所有属性通过props传递
```
<Person {...person}/>
```
* 组件的组合
* 组件标签中包含子组件标签
* 拆分组件: 拆分界面, 抽取组件
* 通过props传递数据
* refs
* 组件内包含ref属性的标签元素的集合对象
* 给操作目标标签指定ref属性, 打一个标识
* 在组件内部获得标签对象: this.refs.refName(只是得到了标签元素对象)
* 作用: 操作组件内部的真实标签dom元素对象
* 事件处理
* 给标签添加属性: onXxx={this.eventHandler}
* 在组件中添加事件处理方法
```
eventHandler(event) {
}
```
* 使自定义方法中的this为组件对象
* 在constructor()中bind(this)
* 使用箭头函数定义方法(ES6模块化编码时才能使用)
* state
* 组件被称为"状态机", 页面的显示是根据组件的state属性的数据来显示
* 初始化指定:
```
constructor() {
super();
this.state = {
stateName1 : stateValue1,
stateName2 : stateValue2
};
}
```
* 读取显示:
this.state.stateName1
* 更新状态-->更新界面 :
this.setState({stateName1 : newValue})
* 实现一个双向绑定的组件
* React是单向数据流
* 需要通过onChange监听手动实现
* 组件生命周期
* 组件的三个生命周期状态:
* Mount:插入真实 DOM
* Update:被重新渲染
* Unmount:被移出真实 DOM
* 生命周期流程:
* 第一次初始化显示
```
constructor()
componentWillMount() : 将要插入回调
render() : 用于插入虚拟DOM回调
componentDidMount() : 已经插入回调
```
* 每次更新state
```
componentWillReceiveProps(): 接收父组件新的属性
componentWillUpdate() : 将要更新回调
render() : 更新(重新渲染)
componentDidUpdate() : 已经更新回调
```
* 删除组件
```
ReactDOM.unmountComponentAtNode(document.getElementById('example')) : 移除组件
componentWillUnmount() : 组件将要被移除回调
```
* 常用的方法
```
render(): 必须重写, 返回一个自定义的虚拟DOM
constructor(): 初始化状态, 绑定this(可以箭头函数代替)
componentDidMount() : 只执行一次, 已经在dom树中, 适合启动/设置一些监听
```
6. ajax
* React没有ajax模块
* 集成其它的js库(如axios/fetch/jQuery/), 发送ajax请求
* axios
* 封装XmlHttpRequest对象的ajax
* promise
* 可以用在浏览器端和服务器
* fetch
* 不再使用XmlHttpRequest对象提交ajax请求
* fetch就是用来提交ajax请求的函数, 只是新的浏览才内置了fetch
* 为了兼容低版本的浏览器, 可以引入fetch.js
* 在哪个方法去发送ajax请求
* 只显示一次(请求一次): componentDidMount()
* 显示多次(请求多次): componentWillReceiveProps()
7. 虚拟DOM
虚拟DOM是什么?
- 一个虚拟DOM(元素)是一个一般的js对象, 准确的说是一个对象树(倒立的)
- 虚拟DOM保存了真实DOM的层次关系和一些基本属性,与真实DOM一一对应
- 如果只是更新虚拟DOM, 页面是不会重绘的
Virtual DOM 算法的基本步骤
- 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
- 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
- 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了
进一步理解
- Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。
- 可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
8. 使用React脚手架创建一个React应用
react脚手架
xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的空项目的库
- 包含了所有需要的配置
- 指定好了所有的依赖
- 可以直接安装/编译/运行一个简单效果
- react提供了一个专门用于创建react项目的脚手架库: create-react-app
- 项目的整体技术架构为: react + webpack + es6 + eslint
创建项目并启动
- npm install -g create-react-app
- create-react-app hello-react
- cd hello-react
- npm start
9. app1: 实现一个评论管理功能
拆分组件:
- 应用组件: App
- 添加评论组件: CommentAdd
- 评论项组件: CommentItem
- 评论列表组件: CommentList
确定组件的state和props:
App:
- state: comments/array
CommentAdd
- state: username/string, content/string
- props: add/func
commentList
- props: comments/array, delete/func
CommentItem
- props: comment/object, delete/func, index/number
编写静态组件
- 拆分页面
- 拆分css
实现动态组件
动态展示初始化数据
- 初始化状态数据
- 传递属性数据
响应用户操作, 更新组件界面
- 绑定事件监听, 并处理
- 更新state
10. app2: 实现github用户搜索功能
拆分组件
- App
- Search
- List
确定组件的state和props
App
- state: searchName/string
Search
- props: setSearchName/func
List
- props: searchName/string
- state: firstView/bool, loading/bool, users/array, errMsg/string
- 编写静态组件
编写动态组件
- componentWillReceiveProps(nextProps): 监视接收到新的props, 发送ajax
- 使用axios库发送ajax请求
11. 组件间通信总结
方式一: 通过props传递
- 共同的数据放在父组件上, 特有的数据放在自己组件内部(state)
- 通过props可以传递一般数据和函数数据, 只能一层一层传递
- 一般数据–>父组件传递数据给子组件–>子组件读取数据
- 函数数据–>子组件传递数据给父组件–>子组件调用函数
方式二: 使用消息订阅(subscribe)-发布(publish)机制: 自定义事件机制
- 工具库: PubSubJS
- 下载: npm install pubsub-js –save
使用:
import PubSub from 'pubsub-js' //引入 PubSub.subscribe('delete', function(data){ }); //订阅 PubSub.publish('delete', data) //发布消息
12. ES6新语法
- const/let
- 解构赋值: let {a, b} = this.props
- 对象的简洁表达
箭头函数:
- 组件的自定义方法: xxx = () => {}
- map/filter的回调方法: (item, index) => {}
优点:
- 简洁
- 没有自己的this,使用引用this查找的是外部this
扩展运算符(…)
- 解构对象: const MyProps = {}, <Xxx {…MyProps}>
- class/extends/constructor/super
- ES6模块化(Babel)
13. 项目打包运行
项目编译打包并运行
- npm build
- npm install -g pushstate-server
- pushstate-server build
- 访问: http://localhost:9000
14. 下载相关模块包
- 创建package.json
react相关库
npm install react react-dom --save
babel相关库
npm install babel-core babel-preset-es2015 babel-preset-react --save-dev
webpack相关库
npm install webpack babel-loader --save-dev npm install webpack-dev-server
15. webpack配置文件: webpack.config.js
const path = require('path'); //path内置的模块,用来设置路径。
module.exports = {
entry: './src/main.js', // 入口文件
output: { // 输出配置
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, 'dist') //输出文件路径配置
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
//babel处理js
{
test: /\.js$/,
exclude: /node_modules/, //排除此文件夹
use: [
'babel-loader'
]
}
]
}
};
16. babel配置文件: .babelrc
{
"presets": ["es2015", "react"]
}
17. 编码
src/js/App.js: 应用组件
import React from 'react' export default function App() { //暴露组件都得使用默认暴露 return <h1>Hello React Client Component</h1> }
src/js/main.js: 入口js
import React from 'react' import ReactDOM from 'react-dom' import App from './App' //渲染组件标签到页面元素 ReactDOM.render(<App />, document.getElementById('demo'))
18. 下载css加载器
npm install style-loader css-loader --save-dev
创建css文件 src/css/test.css
body{
background : red
}
19. 配置webpack-dev-server
devServer:{
contentBase: './',//内置服务器动态加载页面所在的目录
}
``
##20. 执行命令
构建任务:webpack
热加载任务: webpack-dev-server
```
21 package.json: 添加编译/运行脚本
"scripts": {
"start": "webpack-dev-server",
"build": "webpack"
}
react-router使用教程
0. 关于url中#的作用:
- 学习: http://www.ruanyifeng.com/blo…
- ‘#’代表网页中的一个位置。其右面的字符,就是该位置的标识符
- 改变#不触发网页重载
- 改变#会改变浏览器的访问历史
- window.location.hash读取#值
- window.onhashchange = func 监听hash改变
1. reat-router
- github主页: https://github.com/ReactTrain…
- 官网教程: https://github.com/reactjs/re…
- 一峰教程: http://www.ruanyifeng.com/blo…
2. react-router库中的相关组件
包含的相关组件:
- Router: 路由器组件, 用来包含各个路由组件
- Route: 路由组件, 注册路由
- IndexRoute: 默认路由组件
- hashHistory: 路由的切换由URL的hash变化决定,即URL的#部分发生变化
- Link: 路由链接组件
Router: 路由器组件
- 属性: history={hashHistory} 用来监听浏览器地址栏的变化, 并将URL解析成一个地址对象,供React Router匹配
- 子组件: Route
Route: 路由组件
- 属性1: path=”/xxx”
- 属性2: component={Xxx}
- 根路由组件: path=”/”的组件, 一般为App
- 子路由组件: 子<Route>配置的组件
IndexRoute: 默认路由
- 当父路由被请求时, 默认就会请求此路由组件
hashHistory
- 用于Router组件的history属性
- 作用: 为地址url生成?_k=hash, 用于内部保存对应的state
Link: 路由链接
- 属性1: to=”/xxx”
- 属性2: activeClassName=”active”
3. 配置(从官方教程样例中拷贝)
* webpack配置: webpack.config.js
```
module.exports = {
//入口js
entry: './index.js',
//编译打包输出
output: {
filename: 'bundle.js',
publicPath: ''
},
module: {
//使用的loaders
loaders: [
{test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader?presets[]=es2015&presets[]=react'}
]
}
}
```
* 包配置: package.json
```
{
"name": "tutorial",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --inline --content-base ."
},
"author": "",
"license": "ISC",
"dependencies": {
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-router": "^2.0.0"
},
"devDependencies": {
"babel-core": "^6.5.1",
"babel-loader": "^6.2.2",
"babel-preset-es2015": "^6.5.0",
"babel-preset-react": "^6.5.0",
"http-server": "^0.8.5",
"webpack": "^1.12.13",
"webpack-dev-server": "^1.14.1"
}
}
```
4. 编码
* 定义各个路由组件
* About.js
```
import React from 'react'
function About() {
return <div>About组件内容</div>
}
export default About
```
* Home.js
```
import React from 'react'
function Home() {
return <div>Home组件内容2</div>
}
export default Home
```
* Repos.js
```
import React, {Component} from 'react'
export default class Repos extends Component {
render() {
return (
<div>Repos组件</div>
)
}
}
```
* 定义应用组件: App.js
```
import React, {Component} from 'react'
import {Link} from 'react-router'
export default class App extends Component {
render() {
return (
<div>
<h2>Hello, React Router!</h2>
<ul>
<li><Link to="/about" activeClassName="active">About2</Link></li>
<li><Link to="/repos" activeClassName="active">Repos2</Link></li>
</ul>
{this.props.children}
</div>
)
}
}
```
* 定义入口JS: index.js-->渲染组件
```
import React from 'react'
import {render} from 'react-dom'
import {Router, Route, IndexRoute, hashHistory} from 'react-router'
import App from './modules/App'
import About from './modules/About'
import Repos from './modules/Repos'
import Home from './modules/Home'
render((
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home}/>
<Route path="/about" component={About}></Route>
<Route path="/repos" component={Repos}></Route>
</Route>
</Router>
), document.getElementById('app'))
```
* 主页面: index.html
```
<style>
.active {
color: red;
}
</style>
<div id=app></div>
<script src="bundle.js"></script>
```
5. 传递请求参数
* repo.js: repos组件下的分路由组件
```
import React from 'react'
export default function ({params}) {
let {username, repoName} = params
return (
<div>用户名:{username}, 仓库名:{repoName}</div>
)
}
```
* repos.js
```
import React from 'react'
import NavLink from './NavLink'
export default class Repos extends React.Component {
constructor(props) {
super(props);
this.state = {
repos: [
{username: 'faceback', repoName: 'react'},
{username: 'faceback', repoName: 'react-router'},
{username: 'Angular', repoName: 'angular'},
{username: 'Angular', repoName: 'angular-cli'}
]
};
this.handleSubmit = this.handleSubmit.bind(this)
}
handleSubmit () {
const repos = this.state.repos
repos.push({
username: this.refs.username.value,
repoName: this.refs.repoName.value
})
this.setState({repos})
this.refs.username.value = ''
this.refs.repoName.value = ''
}
render() {
return (
<div>
<h2>Repos</h2>
<ul>
{
this.state.repos.map((repo, index) => {
const to = `/repos/${repo.username}/${repo.repoName}`
return (
<li key={index}>
<Link to={to} activeClassName='active'>{repo.repoName}</Link>
</li>
)
})
}
<li>
<form onSubmit={this.handleSubmit}>
<input type="text" placeholder="用户名" ref='username'/> / {' '}
<input type="text" placeholder="仓库名" ref='repoName'/>{' '}
<button type="submit">添加</button>
</form>
</li>
</ul>
{this.props.children}
</div>
);
}
}
```
* index.js: 配置路由
```
<Route path="/repos" component={Repos}>
<Route path="/repos/:username/:repoName" component={Repo}/>
</Route>
```
6. 优化Link组件
* NavLink.js
```
import React from 'react'
import {Link} from 'react-router'
export default function NavLink(props) {
return <Link {...props} activeClassName="active"/>
}
```
* Repos.js
```
<NavLink to={to}>{repo.repoName}</NavLink>
```
使用开源的ant-design库开发项目指南
1. 最流行的开源React UI组件库
material-ui(国外)
ant-design(国内蚂蚁金服)
2. ant-design使用入门
使用create-react-app搭建react开发环境
npm install create-react-app -g
create-react-app antd-demo
cd antd-demo
npm start
搭建antd的基本开发环境
下载
npm install antd --save
src/App.js
import React, { Component } from 'react'; import { Button } from 'antd'; import './App.css'; class App extends Component { render() { return ( <div className="App"> <Button type="primary">Button</Button> </div> ); } } export default App;
src/App.css
@import '~antd/dist/antd.css'; .App { text-align: center; }
实现按需加载(css/js)
使用 eject 命令将所有内建的配置暴露出来
npm run eject
下载babel-plugin-import(用于按需加载组件代码和样式的 babel 插件)
npm install babel-plugin-import --save-dev
修改配置: config/webpack.config.dev.js
// Process JS with Babel. { test: /\.(js|jsx)$/, include: paths.appSrc, loader: 'babel', query: { + plugins: [ + ['import', [{ libraryName: "antd", style: 'css' }]], + ], // This is a feature of `babel-loader` for webpack (not Babel itself). // It enables caching results in ./node_modules/.cache/babel-loader/ // directory for faster rebuilds. cacheDirectory: true } },
去除引入全量样式的语句: src/App.css
@import '~antd/dist/antd.css'
愿你成为终身学习者