明天就是春节了
预祝大家新春快乐 []~( ̄▽ ̄)~*
天天饭局搞得我是身心疲惫 = =
所以更新比较慢
今天想跟大家分享的就是这个大名鼎鼎的 React 框架
React 是这两年非常流行的框架
并不难,还是挺容易上手的
起源于 Facebook 内部项目(一个广告系统)
传统页面从服务器获取数据,显示到浏览器上,用户输入数据传入服务器
但随着数据量增大,越来越难以维护了
Facebook 觉得 MVC 不能满足他们的扩展需求了(巨大的代码库和庞大的组织)
每当需要添加一项新的功能或特性时,系统复杂度就几何增长
致使代码脆弱不堪、不可预测,结果导致他们的 MVC 正走向崩溃
当系统中有很多的模型和相应视图时,其复杂度就会迅速扩大,非常难以理解和调试
总之就是 Facebook 对市场上所有 JS-MVC 框架都不满意,认为都不适合大规模应用
就自己写了一套,用来架设 Instagram 网站
写完后用着用着,发现哎呦这货还真是不错,然后就开源了
随着这几年的沉淀,React 已经变得越来越强大了
科普一下 MVC
MVC 就分为 M、V、C 三部分
简单的理解一下
我们就是 user,在页面中点击了一个按钮触发了事件
控制器 Controller 调整数据,模型 Model 中数据改变
数据改变又会导致视图 View 更新
UI 的改变反馈呈现给我们 user
这里还要说明一下,虽然介绍了 MVC
但是 React 不是 MVC 框架
而是用于构建组件化 UI 的库,是一个前端界面开发工具
顶多算作 MVC 中的 View 视图
而且 MVC 更多的是数据双向绑定
但我们 React 是单向数据流
React 它具有以下特点
我选择使用 webpack 搭建环境
关于 webpack 就不多说了,提一下重点
这是我的 webpack.config.js 配置文件
- module.exports = {
- entry: {
- index: './src/js/entry.js'
- },
- output: {
- path: './static/dist/',
- publicPath: 'http://localhost:8080/static/dist/',
- filename: '[name].js'
- },
- module: {
- loaders: [
- {
- test: /\.js$/,
- loader: 'babel',
- query: {
- presets: ['react', 'es2015']
- }
- },
- {
- test: /.less$/,
- loader: 'style!css!less'
- }
- ]
- }
- }
这里的关键就是除了要安装
和
- babel-loader
还要安装
- babel-core
和
- babel-preset-es2015
用于解析 ES6 语法(为了老版本浏览器)和 React 的 JSX 语法 还有
- babel-preset-react
和
- react
也是必须要下载的
- react-dom
全部依赖模块在这里
- "devDependencies": {
- "babel-core": "^6.22.1",
- "babel-loader": "^6.2.10",
- "babel-preset-es2015": "^6.22.0",
- "babel-preset-react": "^6.22.0",
- "css-loader": "^0.26.1",
- "less": "^2.7.2",
- "less-loader": "^2.2.3",
- "react": "^15.4.2",
- "react-dom": "^15.4.2",
- "style-loader": "^0.13.1",
- "webpack": "^1.14.0",
- "webpack-dev-server": "^1.16.2"
- }
简单说一下 React 的 JSX 语法是个神马东西
可能一会儿大家会看大不明觉厉的代码
比如
- return (
- <div>hehe<div>
- )
这就是 JSX 代码,它是 React 提供的语法糖
是 React 的重要组成部分,使用类似 XML 标记的方式来声明界面及关系
语法糖的意思我写 ES6 的时候也说了
就是计算机语言中添加的语法,对语言的功能没影响
方便我们开发人员使用的,可以增强可读性
如果使用 JS 代码也可以,不过官方推荐使用 JSX
这样来写,结构层次关系都很清晰
webpack 会帮我们把他们转换成浏览器认识的 js 代码(loader 的作用)
(如果好奇转换成了什么,可以去 webpack 输出文件查看,或者找 jsx 转 js 的工具)
JSX 语法结构说的通俗一点就是 HTML、JS 混写
可能大家会有疑惑,说好的结构、样式、行为相分离的前端思想呢?!
React 其中一个主要的设计理念是编写简单容易理解的代码
但我们的组件确实不好松耦合
大家也不要过分较真
这样的语法结构是怎样解析的呢?其实并不神奇
JSX 的语法规则:
不理解不要慌,看了下面就懂了
提前渗透一下
终于写到语法正题了
在此之前我们必须要引用的两个对象
一个 React 核心对象和一个 React-Dom 对象
- var React = require('react');
- var ReactDom = require('react-dom');
ReactDom.render() 是 react 最最基本的方法
所以我放到最开始来讲
它通过 ReactDom 将我们的组件渲染到页面
我在页面中添加一个节点
- <div id="root">
- </div>
现在页面中什么也没有
不过马上就有了
- ReactDom.render(
- <h1>Demo</h1>,
- document.getElementById('demo')
- );
页面中出现了 "Demo"
实际上 react 将我们的节点插入到了 div 节点的内部
React 的一大特点就是组件化
React 组件有以下特点
React.createClass() 就是用于将代码封装成组件 Component 的方法
它会生成一个 React 组件
- var App = React.createClass({
- render: function(){
- return (
- This
- is a component...
- )
- }
- });
这个方法参数是一个对象
对象中有一个 render 返回一个虚拟 DOM
render 是输出组件必须要写的(关于它下面还会再说)
先记住两点
所以各位,下面的写法都是不对的
- //错误的写法
- var app = React.createClass({
- render: function(){
- return
- This
- is a component...
- }
- })
- //错误的写法
- var App = React.createClass({
- render: function(){
- return (
- This
- is a component...
- This
- is also a component...
- )
- }
- });
组件两边加括号的目的,是防止 JavaScript 自动分号机制产生问题
生成的组件要想渲染到页面
就使用我们刚讲完的 ReactDom.render( )
- ReactDom.render(
- <App></App>,
- document.getElementById('root')
- );
为了加以区分,我把标签的属性叫组件特性
- var App = React.createClass({
- render: function(){
- return "demo">This is a component...;
- }
- });
- ReactDom.render(
- ,
- document.getElementById('root')
- );
还要注意两个特例
因为他们是 JavaScript 的保留字
如果想要为组件添加内联样式,可以这样写
- var App = React.createClass({
- render: function(){
- var styles = {
- color: '#fff',
- backgroundColor: '#000'
- }
- return "demo" style={styles}>This is a component...; // <--
- }
- });
- ReactDom.render(
- ,
- document.getElementById('root')
- );
声明了一个 styles 对象
但是将它添加到属性时,要使用 { }
因为 JSX 语法中,html 中使用 js 就必须使用大括号
下面就不再赘述了
组件的属性同样可以像 html 一样添加
并且这个组件属性内部可以通过 this.props 对象获取
- <App name="payen"></App>
- var App = React.createClass({
- render: function() {
- return name: {
- this.props.name
- }
- age: {
- this.props.age
- };
- }
- });
- ReactDom.render("payen"age = "20" > , // <--
- document.getElementById('root'));
了解了这个,我们可以做一个小练习
现在有一组数据,利用它组成一个有序列表组件
- var data = ['Mr.A','Mr.B','Mr.C'];
可以将这个数组成为组件属性
然后利用 this.props.data 获取数据
最后使用 ES5 数组的 map 方法就大功告成了
- var List = React.createClass({
- render: function() {
- return (
- {
- this.props.data.map(function(item, index) {
- return 1000 + index
- } > {
- item
- };
- })
- }
- )
- }
- });
- ReactDom.render( < List data = {
- data
- } > </List>,
- document.getElementById('root')
- );/
还要注意
key 值如果不写的话,虽然可以正常渲染 但会警告我们数组或迭代器的每一项都应该有一个独一无二的 key 值 这里我就使用了 1000 加上索引的形式添加了 key 值
- <li key={1000 + index}>{item}</li>
通常组件的属性与 this.props 对象中的属性是一一对应的
但有一个例外,它是 this.props.children
它表示我们组件的所有子节点
什么意思呢?接着我们上面的例子
我们在 List 组件中添加一些子节点
修改 ReactDom.render( ) 方法的参数
- ReactDom.render(
- <List data={data}>
- <span>Mr.D</span>
- <span>Mr.E</span>
- </List>,
- document.getElementById('root')
- );
我们发现页面中并没有什么变化,但浏览器也没有报错
这时我们需要使用 this.props.children
- var data = ['Mr.A','Mr.B','Mr.C'];
- var List = React.createClass({
- render: function(){
- console.log(this.props.children);
- return (
- {
- this.props.data.map(
- function(item, index){
- return 1000 + index}>{item};
- })
- }
- {
- this.props.children
- }
- )
- }
- });
- ReactDom.render(
- <List data={data}>
- Mr.D
- Mr.E
- </List>,
- document.getElementById('root')
- );
如此页面中就显示出了子节点
这个 this.props.children 很奇怪,它有三种类型值
(可以在控制台上输出验证)
所以我们处理它要特别小心
好在我们可以使用 React 给我们提供的方法
利用 React.Children.map( ) 我们就可以放心遍历处理子节点
- var data = ['Mr.A', 'Mr.B', 'Mr.C'];
- var List = React.createClass({
- render: function() {
- return (
- {
- this.props.data.map(function(item, index) {
- return 1000 + index
- } > {
- item
- };
- })
- } {
- React.Children.map(this.props.children,
- function(child) {
- return {
- child
- }
- })
- }
- )
- }
- });
- ReactDom.render( < List data = {
- data
- } > Mr.D Mr.E < /List>,
- document.getElementById('root')
- );/
组件的属性可以接受任何值,数字、字符串、函数、对象什么都可以
但有时候,我们拿到一个组件,想要验证参数是否符合我们的要求(这其实很重要,不要轻视)
这时就需要使用组件的 propTypes( ) 方法和 React.PropTypes 配合验证了
- var data = ['Mr.A','Mr.B','Mr.C'];
- var App = React.createClass({
- propTypes: {
- data: React.PropTypes.array
- },
- render: function(){
- return (
- {this.props.data}
- )
- }
- });
- ReactDom.render(
- ,
- document.getElementById('root')
- );
这里我期望的 data 属性值为 array 数组类型
没有任何问题,因为我们传入的就是数组
可是如果改成期望字符串类型
- data: React.PropTypes.string
详细见 React 中文官网:
还记得 React 单向数据流的特点么
也就是说我们应该把数据传递给父节点
父节点通过 this.prop 将数据传递给子节点
子节点再通过自己的 this.prop 处理收到的数据
- var data = ['Mr.A', 'Mr.B', 'Mr.C'];
- var List = React.createClass({
- render: function() {
- return (
- {
- this.props.data.map(function(item, index) {
- return 1000 + index
- } > {
- item
- };
- })
- }
- )
- }
- });
- var App = React.createClass({
- render: function() {
- return (
- < List data = {
- this.props.data
- } > </List>
- )
- }
- });
- ReactDom.render(
- ,
- document.getElementById('root')
- );/
所呈现的 DOM 结构
生命周期不难理解
组件的一生无非就是产生、更新、销毁
在组件的每一个生命周期内,都会按顺序触发一些组件方法
比如我们刚刚的 render 方法就会在产生和更新的阶段都会触发
具体触发的回调函数 API 以及作用给大家整理在下面
(关于它们在整个生命周期的触发次数大家应该都能想明白就不写了)
(不常用的我在后面的标注了 * 号)
相当于初始化了组件属性
- return {name: 'payen'}
- this.props.name = 'payen'
相当于初始化了组件状态
- return {show: false}
- this.state.show = false
- this.setState({show: false})
附上一张我盗的图,帮助大家理解(手动滑稽)
关于这些 API 更详细的信息
建议大家可以去 React 中文官网查看:
上面提到了 this.state,和我们之前介绍的 this.props 一样重要
不过 this.props 通常不会变,但 this.state 会变
就如其字面意思,表示组件的状态
这个属性是只读的
所以设置状态我们需要使用 this.setState( )
可使用 this.setState( ) 的方法:
componentWillMount、componentDidMount、componentWillReceiveProps
在此之前我们需要了解的就是 React 的事件系统
JavaScript 原始行间绑定事件都是普遍小写
但我们在 React 中要使用驼峰写法
- <button onclick="clickHandle()"></button>
- <button onClick="clickHandle()"></button>
React 的事件处理器会传入虚拟事件对象的实例(一个对浏览器本地事件的跨浏览器封装)
它有和浏览器本地事件相同的属性和方法,包括 stopPropagation() 和 preventDefault(),
但是没有浏览器兼容问题
详细支持事件见中文官网:
现在我们要来实现这样一个简单的功能
点击按钮,出现弹框
单击弹框,弹框消失
先来实现结构与样式
- var App = React.createClass({
- render: function(){
- return (
- 点击
- )
- }
- });
- var PopUp = React.createClass({
- render: function(){
- var styles = {
- position: 'absolute',
- left: '40px',
- top: '40px',
- width: '100px',
- height: '100px',
- backgroundColor: '#f40'
- }
- return (
- "popup" style={styles}>
- )
- }
- })
- ReactDom.render(
- ,
- document.getElementById('root')
- );
首先我们先来实现第一个功能:点击按钮出现弹框
问题是如何实现
我们的 React 是单向数据流
父级向子级传递数据
最好的办法就是在父级设置组件状态 this.state
将状态通过组件属性 this.props 传递给子级
这样点击事件要做的就是改变父级状态
子级状态也会随之改变
- var App = React.createClass({
- getInitialState: function(){
- return {
- open: false
- }
- },
- buttonHandler: function(){
- this.setState({
- open: true
- });
- },
- render: function(){
- return (
- this.buttonHandler}>点击
- this.state.open}>
- )
- }
- });
- var PopUp = React.createClass({
- render: function(){
- var styles = {
- position: 'absolute',
- left: '40px',
- top: '40px',
- width: '100px',
- height: '100px',
- backgroundColor: '#f40'
- }
- if(this.props.open){
- styles.display = 'block';
- }else{
- styles.display = 'none';
- }
- return (
- "popup" style={styles}>
- )
- }
- })
- ReactDom.render(
- ,
- document.getElementById('root')
- );
第一个功能实现了,再来看第二个
点击弹窗让其消失
同样子级的显示与否掌控在父级手里
要向让子级消失,就必须要改变父级的组件状态 this.state
所以我们必须要把事件函数绑定在父级
再利用组件属性 this.props 传递给子级
完整代码如下
- var App = React.createClass({
- getInitialState: function() {
- return {
- open: false
- }
- },
- buttonHandler: function() {
- this.setState({
- open: true
- });
- },
- popupHandler: function() {
- this.setState({
- open: false
- });
- },
- render: function() {
- return (
- this.buttonHandler
- } > 点击this.state.open
- }
- handler = {
- this.popupHandler
- } >
- )
- }
- });
- var PopUp = React.createClass({
- render: function() {
- var styles = {
- position: 'absolute',
- left: '40px',
- top: '40px',
- width: '100px',
- height: '100px',
- backgroundColor: '#f40'
- }
- if (this.props.open) {
- styles.display = 'block';
- } else {
- styles.display = 'none';
- }
- return ("popup"style = {
- styles
- }
- onClick = {
- this.props.handler
- } > )
- }
- }) ReactDom.render(, document.getElementById('root'));
用一句话来总结一下,就是数据都交给父级来管理
来源: