React 高级指导
1、深入JSX语法
jsx实际上是一个语法糖,babel 将 jsx 转化为 JS,React.createElement 的过程(创建标签和属性)。
如果标签中是大写,说明是一个组件,必须在作用域中声明。组件实际上就是一个函数。
import React from 'react';
function Hello(props) {
return Hello {this.props.name};
}
function HelloWorld() {
return
}
// 只要在定义域内定义一个函数,返回值是一个JSX即可作为一个单向的组件
大括号
JSX中 { } 内部的 JS 语句:if 和 for 不能在大括号内使用(可以使用 && 进行判断)可以在return之前操作 if 和 for等复杂变量处理过程,将需要显示的变量暂时存储在变量中,这样实现条件渲染和循环。
扩展属性
const props = {name: "Mike", age: '16'}; //object
return ;
// 将一个对象作为props进行传递。优点:大型应用数据多,统配向下传递属性,可以方便的进行树结构创建。缺点:很多不相关的属性创建到节点内部。
jsx 中使用 && 进行条件渲染。需要注意:前面的条件必须返回 false 的情况下才不会渲染后面的对象。
{this.props.commentsNumber.length && }
// 当数组的长度是0(空数组),长度是0,界面会渲染一个0,而不会渲染后面的span。
如果想要以jsx的形式在界面上输出 true 或 false null undefined, 那么需要转化成字符串进行输出。否则JSX中进行判断操作。
2、PropTypes监测数据类型
注意: React.PropTypes
自 React v15.5 起已弃用。请使用 prop-types
库代替。
import PropTypes from 'prop-types';
class Hello extends React.Component {
render() {
return {this.props.name};
}
}
Hello.propTypes = {
name: PropTypes.string.isRequired
};
在开发模式下,react会监测传值的类型;
// 你也可以限制你的属性值是某个特定值之一
optionalEnum: PropTypes.oneOf(['News', 'Photos']),
// 限制它为列举类型之一的对象
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
requiredAny: PropTypes.any.isRequired,
3、静态类型检查
- 使用 flow-bin 库检查数据类型:这个比propTypes 强大(facebook)
npm install flow-bin --save-dev flow-bin
npm run flow init# 增加 "flow": "flow" 到 package.json 脚本中
npm install --save-dev babel-preset-flow
# 稍后运行
npm run flow
# 详情查阅 flow 的说明文档
- typeScript - 微软
4、ref
在这三种情况下使用 ref 。尽量减少 ref 的使用。如果必须使用,首先考虑使用状态提升的方式,将子组件的状态放到父组件中,不同组件就可以共享这个状态。ref 的更新在 componentDidMount and componentDidUpdate 阶段。
- 处理焦点、文本选择或媒体控制。
- 触发强制动画。
- 集成第三方 DOM 库
目前使用ref的情况:获取选中文本的位置,获取滚动元素的位置。
不能在函数式组件上使用 ref
// wrong
// 因为函数式组件没有实例,无法获取;可以使用点语法获取当前组件的ref。
this.editor = editor}> //correct
// 直接使用一个变量接一下 a = 组件 使用组件点语法获取 editor 。
this.editor.value...
通常情况下,父组件不会直接获取子组件的属性。这样会保证子组件很好的封装性。
// 子组件最好不要直接调用父组件的函数
this.props.getCommentNumber();
// 可以设置组件的回调函数
// father
onCommentAdded = (response) => {
this.getCommentNumber();
}
// son
this.props.onCommentAdded(response);
// 子组件获取评论数量后,调用组件的回调函数,具体做什么事情需要父组件决定;
父组件获取子组件的ref的情况:父组件需要获取子组件(设置子组件)节点的位置;
可以获取选中的部分,获取选中文本的坐标,计算后设置属性(buttonTop),将这个参数直接传递给子组件中的菜单,以行内样式设置子组件的位置。
不推荐直接获取元素的做法!
5、非受控组件
大部分情况,表单使用受控组件处理(react处理);少数情况使用非受控组件(DOM)处理;
// 非受控组件,不建议下面的写法,需要使用ref
<input type="text" ref={(input) => this.input = input}>
handleClick = () => {
console.log(this.input.value);
}
大部分表单可以写成受控组件。文件上传表单是非受控组件。使用上面的ref方法,将已经上传的内容(文件名),设置组件的属性中。
6、性能优化
1、使用 React 生产版本,使用 uglify-js ,使用其他插件优化代码,去掉注释;
2、避免重复渲染:当一个组件的props和status发生变化后,React会比较前后的差异进行渲染。如果知道某些情况下,即使发生变化也不需要渲染界面,可以手动操作。shouldComponentUpdate 默认是 true。
如果不设置,那么会通过 diff 算法比较整个节点树是否发生变化,稍后判断进行渲染。如果设置false, 直接跳过整个情况(不进行 diff 比较)。
shouldComponentUpdate(nextProps, nextState) {
return true;
}
// return false;
大部分情况下,你可以使用React.PureComponent
而不必写你自己的shouldComponentUpdate
,它只做一个浅比较。但是由于浅比较会忽略属性或状态**突变**的情况,此时你不能使用它。
使用不可突变的数据结构 [Immutable.js] 可以解决状态突变的情况。原始集合在新集合创建后仍然可以使用。新集合和原始集合结构共享。
7、协调 reconciliation
如果完全更新一个DOM树,那么算法的时间复杂度是o(n³)。通过对比不同组件和不同节点是否相同减少计算量。这时的算法复杂度是o(n)。两个重要原理:两个不同类型的元素会产生完全不同的树;根据key的属性,判断稳定的子节点。不同类型的元素直接渲染,相同类型的元素仅仅更新属性。keys是一个索引,在同一个序列中的所有子节点具有不同的keys。使用 map 方法渲染子节点是需要加入唯一的固定的key值。
8、context
避免了props的逐层传递,直接跨越中间的组件进行数据传递。(例如全局的数据,用户的信息,主题,语言等)。用于多个层级多个组件的情况。目前实际开发中还没有用到。
// 常见 context API
const { Provider, Consumer } = React.createContext(defaultValue);
// 创建:渲染 consumer 时,可以根据组件树上层中最接近的组件中匹配 Provider 读取当前的 context 值。如果上层组件树中没有一个 Provider 那么需要用到 default Value。
{value => /* render sth based on the context value */}
对于每一个provider更新,consumer将会更新(不受到props更新的影响)。
9、fragments
一个空的元素。一个组件的根元素必须是一个单独的元素。
function Glossary(props) {
return (
{props.items.map(item => (
- {item.term}
- {item.description}
))}
);
}
React
注意:重点是查漏补缺
http://www.runoob.com/react/react-tutorial.html
安装
1、webpack 编译流程(http://www.runoob.com/react/react-install.html)
// 使用淘宝 cnpm 安装速度较快
npm install -g cnpm --registry=http://registry.npm.taobao.org
npm config set registry https://registry.npm.taobao.org
cnpm install [name]
// 快速搭建 React 开发环境
cnpm install -g create-react-app
create-reast-app my-app
cd my-app/
npm start
localhost://3000
开始界面 index.html
App.js
https://blog.csdn.net/u012859720/article/details/70597119/
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
);
}
}
export default App;
2、忽略文件:这部分文件不会上传到github(对于node-module和个人本地配置文件需要忽略处理);
.gitignore(隐藏文件)
# See https://help.github.com/ignore-files/ for more about ignoring files.
# testing
/coverage
# production
/build
#Idea
/.idea
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
src/config.js
# backup files
*~
ES6 与 传统代码——ES6 需要使用 this.props
function Clock(props) {
return (
{props.data.toLocaleTimeString()}
);
}
class Clock extends React.Component {
render () {
return (
this.props.data.toLocaleTimeString()
);
}
}
JSX 语法补充:
注释写在花括号中,{/* test */} 在测试的时候写jsx注释很方便
React 组件
为了提高代码的复用性,最好使用复合组件;在一个界面中使用多个独立的组件,这样可以最好的做到组件的复用。组件的主要目的就是组件的复用。
/* es5 函数定义组件 */
function Hello(props) {
return (Hello
);
}
/* es6 类定义组件 */
class Welcome extends React.Compoennt {
consotructor(props) {
super(props);
this.state = {
input: this.props.time
};
}
render() {
return (Hello
);
}
}
// 现在常用ES6的方法,使用ES5的方法自己也要会做。
React 受控组件
1.受控组件:在原生的HTML中,input(type='text') textarea select 三个元素的内部的属性可以随着用户的输入变化。
2.react 中使用 state 控制属性的变化,将原生的受控组件和 react 中 state 结合。
class Text extends React.Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleChange = (e) => {
let text = e.target.value.trim();
this.setState({
value: text
});
}
handleSubmit = (e) => {
e.preventDefault();
seafile.addComment(this.state.value).then((res) => {
console.log(res.data.success);
})
}
render() {
}
}
使用受控组件可以过滤用户输入,在handlechange 函数中,设置正则表达式或者简单的trim函数可以过滤部分输入。
是非受控组件。将本地的n个文件上传到服务器中。
当界面上有多个input时,可以只写一个函数,handleinputChange 处理所有的input输入。通过 event.target.type 或者 event.target.name 进行选择,判断是哪个组件发生输入并进行不同的界面操作。
<input type="textarea" name="comment" onChange="handleInputChange"/>
<input type="checkoutbox" name="comment-number" onChange="handleInputChange"/>
handleInputChange = (e) => {
let value = e.target.type === 'textarea' ? e.target.value : e.target.checked;
const name = e.target.name;
this.setState({
//
});
}
受控组件使用很多会形成很多的处理函数(2n)根据场合使用受控组件和非受控组件。
React组合&继承
React中使用组件进行数据传递。可以进行组件的嵌套组合,实现功能的复用(函数、属性);react中不推荐使用继承的方法。
对于对话框和侧旁栏,使用前可能不清除内部的功能和需要的资源。
React 理念
- 静态界面:使用props进行单向数据传递,从顶向下进行数据传递,不需要使用state。
- 动态界面:使用props进行从上到下传递(repoID),state进行组件内部的数据产生和传递(comment)。简单界面使用从上到下的开发过程,复杂界面使用从下到上的开发流程。尽量减少state使用:确定state的使用位置,确定使用state的组件。
- 组件化会增加代码复用性(后期开发显著增加代码复用性,初期代码比较复杂)
开发流程
- UI 设计:根据UI中不同图层,确定组件的相对位置;根据产品功能,根据后端传来的数据(默认是字典的JSON),确定组件的内容。基本上,一个组件做到实现一个单一的功能。如果一个组件可以实现很多功能(后期增加很多新的功能,需要拆分成独立功能的子组件)。
- 静态开发:不考虑用户交互的情况,直接静态页面进行开发(listComments),数据自顶向下进行传递。完成静态开发后,可以进行逐步测试,确保当前静态功能的实现(显示当前评论数量,显示当前已有评论,删除评论)。
- 动态开发:主要针对用户输入或者操作。用户在底组件中输入,从下层获取数据直接请求,或者使用父组件的函数统一进行请求。
React 状态提升
需求:react是一个数据单向传值的框架,如果子组件不能直接给父组件传值。
如果内部多个子组件共同使用相同的数据,并且互相影响。那么可以将这部分共有的数据存放在父组件的属性中,将handleChange作为props传递给子组件。不同子组件会更改福钻进的某个参数,这部分参数会影响其他子组件的行为。确保父组件内部的数据改变会改变子组件的属性。
案例:公里和英里进行转换,总数是固定的,不同子组件需要根据单位进行转换,将唯一的数据存放在父组件中,这样可以减少组件传值造成的bug。
tips:在Chrome react 工具可以查看不同组件的传值,不需要每次console.log。
class Text extends React.Compontent {
constrcutor(props) {
super(props);
this.state({
lan: null,
length: 0
});
}
handleInputChange = (e) => {
if (e.target.name === 'zh') {
this.setState({
lan: 'zh',
length: e.target.value
})
}
else if (e.target.name === 'en') {
this,setState({
lan: 'en',
length: e.target.value
})
}
}
render() {
const lan = this.state.lan;
const chinese = lan === "zh" ? length : length * 0.5;
const english = lan === "en" ? length : length * 2;
return (
{chinese}
{english}
);
}
}
不管输入的是英语还是汉语的单位,那么长度会转化成为英语和汉语进行处理显示。
传递Refs
refs 在高阶组件中进行传值。React 如果需要获取界面中的信息(例如元素的实际宽度,那么需要使用Refs进行传值,让子组件获取父组件的高度和滚动的距离)
Render Props
协同
react可以很好的和其他库进行兼容,例如 jquery 或者 backbone。
可访问性
<Lable htmlFor="test">NameLable>
<input id="test" type="text" name="name">input>
焦点边框:点击输入区域会出现边框
解决方案:使用css outline: 0;
// react bubble
import React from 'react';
import ReactDOM from 'react-dom';
import jquery from 'jquery';
class Tmp extends React.Component {
constructor(...args) {
super(...args)
}
}
class LosinForm extends React.Component {
constructor(...args) {
super(...args)
}
// 使用默认的 defauleProps defaultValue defaultChecked 设置表单初始值。
// 当然可以绑定状态 state;
// this.setState({ value: this.props.name });
render() {
return (
);
}
}
$(function() {
ReactDOM.render(
);
});