番外篇:入门React
如有问题或建议,请后台留言,我会尽力解决你的问题。
背景
当你觉得原生js代码乱七八糟的时候,那就是要体验一下React。(秘籍在最后)
目标
- 踢开React的大门。
简介
React 的核心思想是:封装组件。
各个组件维护自己的状态和 UI,当状态变更,自动重新渲染整个组件。
React 大体包含下面这些概念:
- 组件:
- JSX
- Virtual DOM
- Data Flow
经验:
前端框架的基本组成:
组件及其生命周期、样式、路由、网络请求、数据存储和传递。
HelloWorld
import React, { Component } from 'react'; import { render } from 'react-dom'; class HelloMessage extends Component { render() { return <div>Hello {this.props.name}</div>; } } // 加载组件到 DOM 元素 mountNode render(<HelloMessage name="John" />, mountNode);
解析:
- 组件:HelloMessage 就是一个 React 构建的组件,最后一句 render 会把这个组件显示到页面上的某个元素 mountNode 里面,显示的内容就是 <div>Hello John</div>。
- JSX: 将 HTML 直接嵌入了 JS 代码里面(上面的js里就写了个div),这个就是 React 提出的一种叫 JSX 的语法.
- Virtual DOM:
当组件状态 state 有更改的时候,React 会自动调用组件的 render 方法重新渲染整个组件的 UI。
当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 React 实现了一个Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM 上,React 在这个 Virtual DOM 上实现了一个 diff 算法,当要重新渲染组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结构,所以性能会比原生 DOM 快很多。
- Data Flow:
“单向数据绑定”是 React 推崇的一种应用架构的方式。
与webpack结合
package.json看依赖
{ "name": "learning-01", "version": "1.0.0", "description": "", "main": "webpack.config.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack-dev-server --config ./webpack.config.js --mode development --open", "build": "webpack" }, "author": "", "license": "ISC", "devDependencies": { "@babel/core": "^7.3.3", "@babel/preset-env": "^7.3.1", "@babel/preset-react": "^7.0.0", "babel-loader": "^8.0.5", "clean-webpack-plugin": "^1.0.1", "html-webpack-plugin": "^3.2.0", "react-hot-loader": "^4.7.1", "webpack": "^4.29.5", "webpack-cli": "^3.2.3", "webpack-dev-server": "^3.2.0" }, "dependencies": { "react": "^16.8.3", "react-dom": "^16.8.3" } }
webpack.config.js看配置
const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CleanWebpackPlugin = require('clean-webpack-plugin'); module.exports = { entry: './src/index.js', output: { path: __dirname + '/dist', publicPath: '/', filename: 'bundle.js' }, module: { rules: [ { test: /\.(js|jsx)$/, exclude: /node_modules/, use: ['babel-loader'] } ] }, resolve: { extensions: ['*', '.js', '.jsx'] }, plugins: [ new CleanWebpackPlugin(['dist']), new webpack.HotModuleReplacementPlugin(), new HtmlWebpackPlugin({template:'index.html'}) ], devServer: { contentBase: './dist', hot: true } };
基本搭建环境参考(可直接clone)
https://github.com/xbmchina/r...
JSX
- HTML 里的 class 在 JSX 里要写成 className,因为 class 在 JS 里是保留关键字。
- 2.同理某些属性比如 for 要写成 htmlFor。
- 3.属性值使用表达式,只要用 {} 替换 ""
// Input (JSX): var person = <Person name={window.isLoggedIn ? window.name : ''} />; // Output (JS): var person = React.createElement( Person, {name: window.isLoggedIn ? window.name : ''} );
- 使用注释要用 {} 包起来。
{/* child comment, put {} around */}
- 5.React 会将所有要显示到 DOM 的字符串转义,防止 XSS。
<div dangerouslySetInnerHTML={{__html: 'cc © 2015'}} />
- 6.属性扩散
var props = {}; props.foo = x; props.bar = y; var component = <Component {...props} />;
组件
生命周期(主要两个)
componentWillMount
只会在装载之前调用一次,在 render 之前调用,你可以在这个方法里面调用 setState 改变状态,并且不会导致额外调用一次 render
componentDidMount
只会在装载完成之后调用一次,在 render 之后调用,从这里开始可以通过 ReactDOM.findDOMNode(this) 获取到组件的 DOM 节点。
var React = require('react'); var ReactDOM = require('react-dom'); import ComponentHeader from './components/ComponentHeader'; import ComponentFooter from './components/ComponentFooter'; import BodyIndex from './components/BodyIndex'; import BasicExample from './root' export default class Index extends React.Component { constructor(props) { super(props); this.state = { count: props.initialCount }; } //组件即将加载 componentWillMount() { //定义你的逻辑即可 console.log("Index - componentWillMount"); } //组件加载完毕 componentDidMount() { console.log("Index - componentDidMount"); } render() { /* var component; if (用户已登录) { component = <ComponentLoginedHeader/> } else{ component = <ComponentHeader/> } */ return ( <div> <ComponentHeader /> <BodyIndex /> <ComponentFooter /> </div> ); } } ReactDOM.render(<BasicExample/>,document.getElementById('app'))
事件处理
给事件处理函数传递额外参数的方式:bind(this, arg1, arg2, ...)
render: function() { return <p onClick={this.handleClick.bind(this, 'extra param')}>; }, handleClick: function(param, event) { // handle click }
DOM操作
Refs
另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref 属性指定一个名称,然后通过 this.refs.name 来访问对应的 DOM 元素。
比如有一种情况是必须直接操作 DOM 来实现的,你希望一个 <input/> 元素在你清空它的值时 focus,你没法仅仅靠 state 来实现这个功能。
class App extends Component { constructor() { return { userInput: '' }; } handleChange(e) { this.setState({ userInput: e.target.value }); } clearAndFocusInput() { this.setState({ userInput: '' }, () => { this.refs.theInput.focus(); }); } render() { return ( <div> <div onClick={this.clearAndFocusInput.bind(this)}> Click to Focus and Reset </div> <input ref="theInput" value={this.state.userInput} onChange={this.handleChange.bind(this)} /> </div> ); } }
组合组件
使用组件的目的就是通过构建模块化的组件,相互组合组件最后组装成一个复杂的应用。在 React 组件中要包含其他组件作为子组件,只需要把组件当作一个 DOM 元素引入就可以了。
var React = require('react'); var ReactDOM = require('react-dom'); import ComponentHeader from './components/ComponentHeader'; import ComponentFooter from './components/ComponentFooter'; import BodyIndex from './components/BodyIndex'; class Index extends React.Component { //组件即将加载 componentWillMount() { //定义你的逻辑即可 console.log("Index - componentWillMount"); } //组件加载完毕 componentDidMount() { console.log("Index - componentDidMount"); } render() { /* var component; if (用户已登录) { component = <ComponentLoginedHeader/> } else{ component = <ComponentHeader/> } */ return ( <div> <ComponentHeader /> <BodyIndex /> <ComponentFooter /> </div> ); } } ReactDOM.render( <Index />, document.getElementById('app'));
组件间通信
父子组件间通信
- 1.父组件传递到子组件:
就是通过 props 属性传递,在父组件给子组件设置 props,然后子组件就可以通过 props 访问到父组件的数据/方法,这样就搭建起了父子组件间通信的桥梁。
- 2.父组件访问子组件?
用 refs
非父子组件间的通信
使用全局事件 Pub/Sub 模式,在 componentDidMount 里面订阅事件,在 componentWillUnmount 里面取消订阅,当收到事件触发的时候调用 setState 更新 UI。
一般来说,对于比较复杂的应用,推荐使用类似 Flux 这种单项数据流架构
使用css样式
1.内联样式
在render函数里定义
const styleComponentHeader = { header: { backgroundColor: '#333333', color: '#FFFFFF', 'padding-top': '12px', 'paddingBottom: '16px' } };
注意样式的驼峰写法 style = {styleComponentHeader.header}
文件中引用css的样式 注意class需要更改成className确定是动画、伪类(hover)等不能使用
2.内联样式中的表达式
paddingBottom:(this.state.minHeader)?"3px":"15px"
注意好好理解这里的state引起样式的即时变化
3.CSS模块化
原因:避免全局污染、命名混乱、依赖管理不彻底、无法共享变量、代码压缩不彻底
npm install --save-dev style-loader css-loader npm install --save-dev babel-plugin-react-html-attrs //为了使用原生的html属性名
全局样式和局部样式
:local(.normal){color:green;} //局部样式 :global(.btn){color:red;} //全局样式
CSS模块化的优点 所有样式都是local的,解决了命名冲突和全局污染问题 class名生成规则配置灵活,可以此来压缩class名 只需引用组件的JS就能搞定组件所有的js和css 依然是css,几乎零学习成本
jsx样式与css的互转 工具:https://staxmanade.com/CssToReact/
react-router
官网:https://reacttraining.com/rea...
GitHub:https://github.com/ReactTrain...
注意点:
1.引用的包是有区别的。
2.控制页面的层级关系 单页面构建Router控制
底层机制 React: state/props -> Components ->UI Router: location ->Router -> UI
3.router传参
定义: path="list/:id" 使用: this.props.match.params.id
4.地址无法刷新(巨坑)
表现:'/' 根节点 Home 显示无误,不过其他任何路由 例如 /detail,均显示 Cannot GET /detail。
解决方法:
- 4.1 用的 BrowserRouter 改为 HashRouter 即可
- 4.2 修改 webpack.config.js 配置文件
module.exports = { // 省略其他的配置 devServer: { historyApiFallback: true } }
详情可以参考:https://blog.csdn.net/zwkkkk1...
网络请求Fetch
官网:https://github.com/github/fetch
fetch('/users.json') .then(function(response) { return response.json() }).then(function(json) { console.log('parsed json', json) }).catch(function(ex) { console.log('parsing failed', ex) })
Redux
下期再讲
学习资料
练习代码
学习Demo样例:https://github.com/xbmchina/r...
项目Demo样例:https://github.com/xbmchina/r...
React相关资料
**React官网:https://reactjs.org/docs/hell...
React中文网:https://react.docschina.org/
React学习文档:http://caibaojian.com/react/
webpack搭建React:https://segmentfault.com/a/11...
React-router官网:https://reacttraining.com/rea...
阿里UI库Ant Design:https://ant.design/index-cn
阿里图标库:https://www.iconfont.cn/
谷歌的ReactUI库:https://material-ui.com/
css转React:https://staxmanade.com/CssToR...
Fetch请求:https://github.com/github/fetch**
最后
本人水平有限,欢迎各位建议以及指正。顺便关注一下公众号呗,会经常更新文章的哦。