React一些特点
- 专注于视图层
- 业务框架(也可以理解为model层)可以根据业务场景自行选择。
- Virtual DOM提高性能。
- 便于和其他平台集成
- 基于VD我们可以渲染出不同的原生控件
- 即组件在输出的时候,究竟是输出成web DOM,还是安卓控件,还是IOS控件,都有平台本身决定。
- learn once, write anywhere
JSX
概念
- 就是将HTML语法直接加入到JavaScript中,然后再通过编译器转换为纯JS后有浏览器执行。
写法注意点:
-
定义标签时,只允许被一个标签包裹,即必须只有一个root节点
-
错误写法
<span>11</span> <span>22<span>
-
正确
<div> <span>111</span> <span>222<span> </div>
-
-
标签必须闭合,即单标签必须有 />
-
错误
<Com >
-
正确
<Com />
-
-
DOM元素用小写字母开头,组件元素用大写字母开头
-
命名空间,解决命名冲突或对一类组件进行分类
//namespace.js import React, { Component } from 'react'; const com1 = () => { return( <div> NameSpace命名空间下的com1组件 </div> ) } const com2 = () => { return( <div> NameSpace命名空间下的com2组件 </div> ) } export const NameSpace = {com1, com2}; // app.js import { NameSpace } from './namespace.js'; render() { return() { <NameSpace.com1 /> <NameSpace.com2 /> } }
-
注释
-
DOCTYPE(有些特殊,后续会说到)
元素属性
改变
- class属性改为className
- for属性改为htmlFor
写法
-
自定义属性:
-
小驼峰写法
-
往DOM元素中传入自定义属性,是不会渲染的;但如果要使用HTML自定义属性,则需要加上data-前缀
<div d=”xxx”>content</div> // × <div data-d=”xxx”>content</div> //√
-
-
boolean属性:默认为false
/** 如果写<Checkbox checked={true} /> 直接写为<Checkbox checked /> */ <Checkbox checked={true} /> /** 如果需要<Checkbox checked={false} /> 那么直接写<Checkbox />即可,直接省略checked属性*/ <Checkbox />
同类型还有disabled, required, readOnly, selected等。
-
rest写法提升效率
const data = { name: ‘foo’, value: ‘bar’ }; const Com = <Component {...data} />
-
属性表达式:凡是用到js语法的,都需要在外面包一层{}
组件
Web Component规范
- 这个规范是W3C用来统一web端组件定义的。它通过自定义组件的方式来统一组件,现在的三大框架也都在向各个规范靠近并逐渐趋于稳定。
-
在该规范中,一共定义了以下四个方面。具体定义的内容我没有看。这里只是先做一个了解
WebComponent包括的内容 - 在平时写组件时,每个自定义的元素对外提供属性,方法,事件,而内部就专注于实现功能,以此来完成对组件的封装。这其实就是在遵循Web Component规范~
组件的创建
- React组件的组成
- 注意react组件与Web Component传达的思想一样,但具体实现不同。
- 创建方法
-
ES6 Classes
import React, { Component } from 'react'; import PropTypes from 'prop-types'; class Com extends Component { constructor(props) { super(props); } render() { return( <div>COM1</div> ) } }
-
无状态函数
const Com = (props) => { return( <div>{ ...props }</div> ) }
-
区别:
diffrence
-
数据流
单项数据流
- 在React中数据是自顶向下单向流动的,即从父组件到子组件
- 我们可以把所有的组件按照父子想象成一棵树,就像家族树一样,所有的数据就像基因一样,只能爸爸传给儿子,儿子可不能给粑粑。
states
- setState
-
改变内部状态,即state值,执行完毕后最大的表现就是该组件会重新渲染。
-
该方法是异步方法,一个生命周期内所有的setState方法会合并操作。所以这里容易犯一个错误,即后一个state依赖前一个state,举个例子:
this.state = { basic: 0, number: 1 } componentDidMount() { this.setState({ basic: 12, number: basic + 10 }) }
我们本来想得到number=22,但其实得到了number=1,就是因为这个方法是一个异步方法,不是同步的,所以number并不会等basic复制完毕之后再复制,而是number和basic同时执行的。
-
props
- 外部传进来的组件的属性(对比函数,就像入参一样)
- 是react数据流主要的流动管道。
children属性
-
是React内置的,代表子组件的集合。
// com.js render() { console.log(this.props.children); //[{...}, {...}] return( <div>{this.props.children}</div> ) } //App.js import Com from './com.js'; <Com> <button>click</button> <input /> </ Com>
-
用函数式编程的方法来动态渲染数据
React.Children.map(children, (child) =>{})
组件prop
-
也可以将组件作为prop传入
<TabPane tabNav={<span><i className="fa fa-home"/> home </span>} />
-
父子组件通信
-
父 -> 子:prop
-
子 -> 父:callback回调
// father.js import Child from './child.js'; handleChange = (props) => { console.log('子组件返回的prop', props); // 3 } <Child data={2} onChange={this.handleChange} /> //child.js this.state = { count: 0 } <button onClick=() => { this.setState({ count: this.props.data +1 }); this.props.onChange(this.state.count) }>改变data</button>
-
propTypes
import PropTypes from 'prop-types';
static propTypes = {
name: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.arrayOf(PropTypes.node),
PropTypes.func
]).isRequired
}
// 默认属性值
static defaultProps = {
name: 'foo',
value: 'bar'
}
React生命周期
-
整体流程
lifecycle.png -
代码示例加个生命周期讲解(每个生命周期的注释)
class App extends Component { static propTypes = { // ... }; static defaultProps = { // ... } constructor(props) { super(props); this.state = { top: 1 } } componentWillMount() { // 组件挂载之前,在render方法之前执行 // 不用在这里setState,因为初始化渲染的时候组件只渲染一次(render),所以这是无意义的执行,初始化的state直接放到this.state里就好了 console.log('---before mount---', this.state, this.props); // top: 1, data: 2 this.prevProps = this.props; this.prevState = this.state; } componentDidMount() { // 组件挂载之后,在render后执行 // 如果在这里setState,那么就相当于组件光在初始化阶段就渲染了两次,并不好。 // 但有些情况必须在这里setState,如计算组件的位置,宽高等这些需要组件渲染出来后才能得到的信息,就不得不让组件先渲染,然后更新信息,再次渲染。 const top = this.refs['div'].getBoundingClientRect().top; this.setState({ top }) console.log('---after mount---', this.state.top, this.props.data); } componentWillReceiveProps(nextProps) { // 在组件更新之前,即componentWillUpdate之前,会先执行该函数 console.log('----传入prop之后,渲染之前----'); console.log(nextProps); // 该函数可以作为props改变后,渲染之前改变state的机会。 this.setState({ top: nextProps.data }) } shouldComponentUpdate(nextProps, nextState) { // 性能优化点,该函数接收更新的props和states,让我们可以作必要的条件判断处理,让组件在必要时更新,不必要时不更新 // 返回true -> 更新 , 返回false -> 不更新 let flag = false; if(this.prevProps.data !== nextProps.data || this.prevState.top !== nextState.top) { flag = true; } else { flag = false; } this.prevProps = nextProps; this.prevState = nextState; console.log('是否需要更新---', flag) return flag; } componentWillUpdate(nextProps, nextState) { // 组件更新之前,入参为需要更新的值 // 不可以在这里setState,不然会死循环,具体在Chapter3中说 console.log('----before update----', nextProps, nextState); } componentDidUpdate(prevProps, prevState) { // 组件更新之后,入参为更新前的值 // 如果在这里setState的话,必须在shouldComponentUpdate里用某一条件卡住,不然会死循环。 console.log('----after update----'); this.setState({ top: 500 }) } render() { console.log('----render----') return ( <div ref="div" className="div" style={{top: this.state.top}}> { this.state.top } hey, I'm testing life cycle in React! </div> ) } }
-
补充
- componentWillMount -> render -> componentDidMount: 组件的初始化阶段
- 如果组件自身state更新了,会依次执行componentWillReceiveProps ->shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
- DOM真正被添加到HTML中的方法是,componentDidMount和componentDidUpdate方法。(render方法的目的是输出Virtual DOM)
React-DOM
-
方法
-
findDOMNode: 返回该组件实例对应的DOM节点,既然是DOM节点,所以只对已经挂载的组件有效(即只能在上述两个生命周期方法中调用)
-
ReactDOM.render:只有在顶层组件中才会用到,因为要把react渲染的virtualDOM渲染到浏览器的真实DOM中。
ReactDOM.render(<App />, document.getElementById('root'), callback); // 组件装载完毕,callback调用,该方法返回APP组件的实例
- 亮点: 在每次更新时,React不会把整个组件重新渲染一边,而会使用DOM diff算法做局部更新。
- unmountComponentAtNode: 进行卸载操作
- 不稳定方法 unstable_renderSubtreeIntoContainer
-
顾名思义,不稳定的_将子组件渲染到容器中
-
用法:
ReactDom.unstable_renderSubtreeIntoContainer( parentComponent, nextElement, container, callback ) eg: // portal.js ReactDom.unstable_renderSubtreeIntoContainer( this, children, document.createElement('div'), this.props.onUpdate ) //App.js <Portal> <Modal> Modal content </Modal> </Portal> /** 就是在Portal组件中,将它的子组件插入新建的div中,Portal并不关心它的子组件是啥*/
-
作用:
- 从上面可以看出,Portal只是一个壳,它把modal的行为抽象了出来,即我们在Portal组件中通过该方法将内部的子组件放到了指定DOM节点上。而Portal本身并不对应任何一个DOM,只是作为一个中介控制modal的行为和把它渲染到另一DOM上。
-
所以总结来说,该方法的作用就是
- 更新组件到传入的DOM节点上
- 完成了在组件内实现跨组件的DOM操作
-
-
refs
概念
- 组件被调用是会创建一个该组件的实例,而refs(即reference,引用)指向这个实例。
形式
-
可以是回调函数,在组件被挂载后执行
<input ref={(ref) => this.textInput = ref;} />
-
可以是字符串
<input ref="input" /> -
不同组件类型得到的东西不一样
-
如果ref在原生DOM组件上,则会得到DOM节点
<buttton>...</button>
-
如果ref在React组件上,则会得到该组件实例
注意:如果组件为无状态组件,则给它上加ref的话,为undifined,无效。因为它有组件类实例化的过程,组件的挂载只是方法调用而已。Button { context: {}, props: {}, refs: {}, children: {}, ... }
-
与findDOMNode的区别
-
首先要明白实例和DOM节点不是一个东西。
-
ref得到的是实例,就像上面的Button{...}一样,而如果改为findDOMNode方法,得到的是DOM,即就会是:<button>...</button>
-
不要把ref和findDOMNode混用,即
const comRef = this.refs.com; const comDom = ReactDOM.findDOMNode(comRef); // do something with comDom
- 一来,我们本来写这个组件是要具有封装性的,而如此在外面得到了该组件的DOM进行一系列操作,则打破了它的封装性。
- 二来,我们打破了父子组件仅通过props通信的原则。
安全与性能
安全
- 防止xss攻击
- react会将所有显示到DOM上的字符转义,防止XSS。所以当我们需要在JSX中用字符实体时,eg: © ; (©)版权标识,,那肯定不会正确显示,因为react把里面的字符实体给转换了。
- 解决:
-
直接使用对应字符的UTF-8编码
-
使用对应字符的unicode编码
-
使用数组组装
<div>{[‘cc’, <span>©</span>, ‘2019’]}</div>
-
直接插到html里
<div>cc©2019</div>
-
dangerouslySetInnerHTML属性
名字起的很贴切,作用就是避免react转义字符<div dangerouslySetInnerHTML={__html: 'cc © 2019'} />
-
性能
-
在合适的情况下,尽可能使用无状态函数创建组件,因为它自始至终只保持了一个实例,减少了内存分配和一些无谓的检查,做到了内部优化。
-
合理使用shouldComponentUpdate,减少不必要的组件渲染。
- 无状态函数没有生命周期,所以没有该方法。
- 但可以使用Recompose库的pure方法:/** 该方法将无状态组件转换成class语法加上PureRender后的组件*/ const OptimizedCom = pure(ExpensiveCom);
注意点
- 组件的最终目的是输出virtual DOM,即需要被渲染到界面的结构(后面章节会说到VD在js中的具体实现)。而做这件事的就是render,核心的渲染函数。
- 在进行一些DOM API调用时,多想想自己为啥要操作DOM。因为DOM操作很费性能。
网友评论