React
简介由于以前作了挺久的Vue
开发。如今开始学习React
也是并不难的。首先要对React
的总体脉络要有个大概的了解。(注:这里只作学习的记录和总结,有些可能会遗漏,后期有了新的感悟也会慢慢完善,一些基础的语法的使用仍是要看官方网站为主)。javascript
从上图能够得知React
分为三大致系:css
React.js
ReactNative
ReactVR
学习的顺序也是React.js
->ReactNative
->ReactVR
按部就班的。html
React
的基础大致包括下面这些概念:JSX
Virtual DOM
Data Flow
React.js
不是一个框架,它只是一个库。它只提供UI
(view)层面的解决方案。在实际的项目当中,它并不能解决咱们全部的问题,须要结合其它的库,例如Redux
、React-router
等来协助提供完整的解决方法。前端
React
开发环境的搭建React
<!DOCTYPE html>
<html> <head> <meta charset="UTF-8"> <!--https://cdnjs.com/--> <script src="./js/react.development.js"></script> <script src="./js/react-dom.development.js"></script> <script src="./js/browser.min.js"></script> <script src="./js/prop-types.min.js"></script> </head> <body> <div id="root"></div> <!--凡是使用 JSX 的地方,都要加上 type="text/babel"--> <script type="text/babel"> class Hello extends React.Component { render() { return (<h1>Hello,{this.props.name}</h1>) } } ReactDOM.render( <Hello name="zhangsan" />, document.getElementById('root') ); </script> </body> </html> 复制代码
上面代码一共用了四个库:react.js
、react-dom.js
、browser.js
和prop-types.min.js
,它们必须首先加载。vue
react.js
:
React
的核心库。
react-dom.js
:负责
Web
页面的
DOM
操做。
browser.js
:将
JSX
语法转为
JavaScript
语法。
prop-types.min.js
:是传入的
props
类型的验证。自
React v15.5
起,
React.PropTypes
已移入另外一个包中,使用
prop-types
库代替。
须要注意的是从
Babel 6.0
开始,再也不直接提供浏览器直接编译的JS
版本,而是要用构建工具构建出来。这里只作演示用。建议学习开发的时候也不要使用浏览器直接引入的方式。java
node.js
npm
create-react-app
,相似于
Vue
的
vue-cli
npm install -g create-react-app
// 在任意文件夹中使用create-react-app新建项目 create-react-app demo 复制代码
安装成功以后,
npm start
或者yarn start
就能够启动项目了。node
create-react-app
生成的目录结构以下:react
demo/ README.md package.json yarn.lock .gitignore node_modules/ public/ favicon.ico index.html ... src/ App.css App.js App.test.js index.css index.js logo.svg serviceWorker.js 复制代码
package.json
安装的React
依赖介绍以下:webpack
从package.json
的dependencies
能够看出来脚手架工具默认安装了React
须要的依赖。下面就介绍这些核心依赖的做用:git
react
:是
React
的核心库。
react-dom
:负责
Web
页面的
DOM
操做。
react-scripts
:生成项目全部的依赖。例如
babel
,
css-loader
,
webpack
等从开发到打包前端工程化所须要的
react-scripts
都帮咱们作好了。
如今就能够在App.js
里编写咱们本身的代码了,以下(已修改生成的源码):
在项目根目录执行npm start
(若安装了yarn
可以使用yarn start
),打开http://localhost:3000
就能够看到首页了:
建议你们参考Create React App 中文文档
要想理解JSX
的由来,就要先介绍一下Virtual DOM
。
一个真实页面对应一个DOM
树。在传统页面的开发模式中,每次须要更新页面时,都要手动操做DOM
来进行更新。DOM
操做很是昂贵。并且这些操做DOM
的代码变得难以维护。
React
把真实DOM
树转换成JavaScript
对象树,也就是Virtual DOM
。以下图:
每次数据更新后,从新计算Virtual DOM
,并和上一次生成的Virtual DOM
作对比,对发生 变化的部分作批量更新。VirtualDOM
不只提高了React
的性能,并且它最大的好处其实还在于方便和其余平台集成(好比react-native
是基于Virtual DOM
渲染出的原生控件)。
所以在
Virtual DOM
输出的时候,是输出Web DOM
,仍是Android
控件,仍是iOS
控件,由平台自己决定。
Web
页面是由一个个HTML
元素嵌套组合而成的。当使用JavaScript
来描述这些元素的时候,这些元素能够简单地被表示成纯粹的JSON
对象。好比,咱们如今须要描述一个按钮(button
),用HTML
语法表示很是简单:
<button class="primary">
<em>submit!</em> </button> 复制代码
其中包括了元素的类型和属性。若是转成JSON
对象,会包括元素的类型以及属性:
{
type: 'button', props: { className: 'primary', children: [{ type: 'em', props: { children: 'submit' } }] } } 复制代码
上面的JS
对象表达了一个按钮功能。在表达还不怎么复杂的结构时,书写就已经很难受了。这让咱们想起使用HTML
编写结构时的简洁。因此JSX
语法为此应运而生。假如咱们使用JSX
语法来从新表达上述button
元素,只需下面这么写:
ReactDOM.render(
<button className="primary"> <em>submit!</em> </button> , document.getElementById('root')) 复制代码
JSX
将HTML
语法直接加入到JavaScript
代码中,会让代码更加直观并易于维护。经过编译器转换到纯JavaScript
后由浏览器执行。JSX
在产品打包阶段都已经编译成了纯JavaScript
。
注意:
JSX
是JavaScript
语言的一种语法扩展,长得像HTML
,但并非HTML
。尽管JSX
是第三方标准,但这套标准适用于任何一套框架。如今已所有采用Babel
的JSX
编译器来实现对JSX
语法的编译。
class Test extends React.Component {
constructor(props) { super(props) } render() { return ( <div> <h1 className='title'>Hello,React</h1> </div> ) } } ReactDOM.render(<Test />, document.getElementById('root')) 复制代码
上面的代码通过编译之后会变成以下:
class Test extends React.Component {
constructor(props) { super(props) } render() { return ( React.createElement( 'div', null, React.createElement( 'h1', { className: 'title' }, 'Hello,React' ) ) ) } } ReactDOM.render(<Test />, document.getElementById('root')) 复制代码
React.createElement
会构建一个JavaScript
对象来描述HTML
结构的信息,包括标签名、属性、还有子元素等。这样的代码就是合法的JavaScript
代码了。
因此从JSX
到页面通过了以下图的过程:
ReactDOM.render(<div className="foo">Hello</div>, document.getElementById('root'))
复制代码
HTML
里的class
在JSX
里要写成className
,由于class
在JS
里是保留关键字。同理某些属性好比for
要写成htmlFor
。
JavaScript
表达式JSX
遇到HTML
标签(以<
开头),就用HTML
规则解析。遇到代码块(以 {
开头),就用 JavaScript
规则解析。
const names = ['zhangsan', 'lisi', 'wangwu']
ReactDOM.render( <div> { names.map((name,key) => { return <div key={key}>Hello,{name}</div> }) } </div>, document.getElementById('root') ) 复制代码
JSX
容许直接在模板插入JavaScript
变量。若是这个变量是一个数组,则会展开这个数组的全部成员。
const names = [<div key="1">zhangsan</div>, <div key="2">lisi</div>] ReactDOM.render( <div> {names} </div>, document.getElementById('root') ) 复制代码
在JSX
里使用注释也很简单,就是沿用JavaScript
,惟一要注意的是在一个组件的子元素位置使用注释要用 {}
包起来。
const App = (
<Nav> {/* 节点注释 */} <Person /* 多行 注释 */ name={window.isLoggedIn ? window.name : ''} /> </Nav> ) 复制代码
HTML
转义React
会将全部要显示到DOM
的字符串转义,防止XSS
。因此若是JSX
中含有转义后的实体字符好比 ©
(©) 最后显示到 DOM
中不会正确显示,由于React
自动把©
中的特殊字符转义了。可使用dangerouslySetInnerHTML
来实现。
ReactDOM.render(
<div dangerouslySetInnerHTML={{ __html: '© 2020' }} />, document.getElementById('root') ) 复制代码
若是在JSX
中使用的属性不存在于HTML
的规范中,这个属性会被忽略。若是要使用自定义属性,能够用data-
前缀。可访问性属性的前缀aria-
也是支持的。
ReactDOM.render(<div data-attr="abc">content</div>, document.getElementById('root'))
复制代码
React
认为组件是和模板紧密关联的,组件模板和组件逻辑分离让问题复杂化了。React
容许将代码封装成组件(component
),而后像插入普通 HTML
标签同样,在网页中插入这个组件。一个React
应用就是构建在React
组件之上的。Component
(组件)能够是类组件(class component
)、函数式组件(function component
)。
组件有三个核心概念,React
组件基本上由组件的构建方式
、组件内的属性状态
与生命周期方法
组成。以下图:
props
(属性)
states
(状态)
注意:组件生成的
HTML
结构只能有一个单一的根节点。在React
中,数据是自顶向下单向流动的,即从父组件到子组件。
官方在React
组件构建上提供了3
种不一样的方法:React.createClass
、ES6 classes
和无状态 函数(stateless function
)。
官方在
React@15.5.0
后不推荐用React.createClass
,建议使用ES6 class
,这里不作具体介绍。
ES6 classes
class Test extends React.Component {
constructor(props) { super(props) } render() { return (<h1>Hello,React</h1>) } } 复制代码
能够用纯函数来定义无状态的组件(stateless function
),这种组件没有状态,没有生命周期,只是简单的接受props
渲染生成DOM
结构。无状态组件很是简单,开销很低。好比使用箭头函数定义:
const Hello = (props) => <div>Hello {props.name}</div>
ReactDOM.render(<Hello name="张三" />, document.getElementById('root')) 复制代码
props
就是组件的属性,由外部经过JSX
属性传入设置,一旦初始设置完成,就能够认为this.props
是不可更改的,因此不要轻易更改设置this.props
里面的值。
class Hello extends React.Component {
constructor(props) { super(props) } render() { return ( <h1>Hello,{this.props.name}</h1> ) } } // PropTypes 验证,若传入的props type不是string将提示错误 Hello.propTypes = { name: PropTypes.string } // Prop 初始值,若是 prop 没有传入值將會使用 default 值 lisi Hello.defaultProps = { name: 'lisi' } ReactDOM.render(<Hello name="张三" />, document.getElementById('root')) 复制代码
state
state
是组件的当前状态,用this.state
来存取state
。一旦状态(数据)更改,组件就会自动调用 render
从新渲染UI
,这个更改的动做会经过this.setState
方法来触发。
下面代码是一个1000
毫秒就會加一的累加器:
class Timer extends React.Component {
constructor(props) { super(props) // 须要自行绑定 this context this.tick = this.tick.bind(this) this.state = { count: 0 } } // 累加器方法,每一秒会使用 setState() 更新内部 state,让 Component 从新 render tick() { this.setState({ count: this.state.count + 1 }) } // 生命周期函数 componentDidMount() { this.interval = setInterval(this.tick, 1000); } // 生命周期函数 componentWillUnmount() { clearInterval(this.interval); } render() { return ( <div>Count: {this.state.count}</div> ) } } ReactDOM.render(<Timer />, document.getElementById('root')) 复制代码
每一个组件都包含都包含组件生命周期方法,在运行过程当中特定的阶段执行这些方法。
组件的生命周期分为四类,共有10个方法:
下面会依次介绍这几类组件的生命周期所调用的函数。
当组件实例被建立并插入DOM
中时,其生命周期调用顺序以下:
constructor()
static getDerivedStateFromProps()
:会在调用
render
方法以前调用,而且在初始挂载及后续更新时都会被调用。
render()
:是
class
组件中惟一必须实现的方法。
componentDidMount()
:在组件挂载后(插入
DOM
树中)当即调用。
当组件的props
或state
发生变化时会触发更新。组件更新的生命周期调用顺序以下:
static getDerivedStateFromProps()
shouldComponentUpdate()
:当
props
或
state
发生变化时,
shouldComponentUpdate()
会在渲染执行以前被调用。
render()
getSnapshotBeforeUpdate()
:在最近一次渲染输出(提交到
DOM
节点)以前调用。
componentDidUpdate()
:会在更新后会被当即调用。首次渲染不会执行此方法。
当组件从DOM
中移除时会调用以下方法:
componentWillUnmount()
:在组件卸载及销毁以前直接调用。
当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用以下方法:
static getDerivedStateFromError()
:今生命周期会在后代组件抛出错误后被调用。
componentDidCatch()
:今生命周期在后代组件抛出错误后被调用。
这里只作简单介绍,后面会单独写文章详细记录生命周期的使用方式。
React
里面绑定事件的方式和在HTML
中绑定事件相似,使用驼峰式命名指定要绑定的onClick
属性为组件定义的一个方法。下面是一个数字累加的小例子:
class AddCount extends React.Component {
constructor(props) { super(props) this.state = { count: 0 } } handleClick(e) { this.setState({ count: this.state.count + 1 }); } render() { return ( <div> <p>Count: {this.state.count}</p> <button onClick={this.handleClick.bind(this)}>Click me</button> </div> ) } } ReactDOM.render(<AddCount />, document.getElementById('root')) 复制代码
注意要显式调用
bind(this)
将事件函数上下文绑定要组件实例上,这也是React
推崇的原则:没有黑科技,尽可能使用显式的容易理解的JavaScript
代码。
DOM
操做有时候咱们避免不了要直接操做DOM
。React
也提供了几种咱们能够直接操做DOM
的方式。
findDOMNode
当组件加载到页面上以后(mounted
),能够经过react-dom
提供的findDOMNode()
方法拿到组件对应的DOM
元素。
class Test extends React.Component {
constructor(props) { super(props) } componentDidMount() { const el = ReactDOM.findDOMNode(this) // 123456 console.log(el.textContent) } render() { return ( <div>123456</div> ) } } ReactDOM.render(<Test />, document.getElementById('root')) 复制代码
注意:
findDOMNode()
不能用在无状态组件上。findDOMNode
仅在组件始终返回永不更改的单个DOM
节点时才起做用。在<React.StrictMode>
严格模式下,这个方法已经被官方弃用,因此在开发中不要使用这个方法。
Refs
另一种方式就是经过在要引用的DOM
元素上面设置一个ref
属性指定一个名称,而后经过 this.refs.name
来访问对应的DOM
元素。
class Test extends React.Component {
constructor(props) { super(props) } componentDidMount() { this.refs.textInput.focus() } render() { return ( <div> <input ref="textInput" /> </div> ) } } ReactDOM.render(<Test />, document.getElementById('root')) 复制代码
不要在 render
或者render
以前访问refs
。不要滥用 refs
。好比只是用它来按照传统的方式操做界面UI:找到 DOM -> 更新 DOM
。
https://zh-hans.reactjs.org/
http://huziketang.mangojuice.top/books/react/
https://www.bilibili.com/video/BV1g4411i7po?p=2
https://www.jianshu.com/p/c6040430b18d
https://www.zhihu.com/question/336664883/answer/790855896
https://book.douban.com/subject/26918038/
https://www.kancloud.cn/digest/babel/217110
http://www.ruanyifeng.com/blog/2015/03/react.html
https://kdchang.gitbooks.io/react101/content/
https://github.com/mocheng/react-and-redux/issues/99
https://juejin.im/entry/587de1b32f301e0057a28897
https://www.html.cn/create-react-app/docs/getting-started/
本文使用 mdnice 排版