概况:
通过本篇文章你可以对react的重点有个整体的认识。 关于react是什么,优点,解决什么问题等,网上一大推就不啰嗦了。
了解虚拟DOM的实现,参考这篇文章
[虚拟DOM]
(https://www.zhihu.com/question/29504639)
简单讲,其实就是用一个轻量级的dom结构(用js模拟实现),来模拟重量级的dom结构,通过比对轻量级结构后,在操作重量级dom结构提高性能,从而到达性能优化的目的。
生命周期:
快速学习react 先了解它的重中之重----生命周期, 一个组件在不同时期会调用不同时期的函数接口也就是对应的生命周期函数
装载时期的函数
getDefaultProps(是设置默认props)getInitialState(废弃,设置默认State) 依次执行以下函数
• constructor
• componentWillMount
• render
• componentDidMount
更新时期的函数
如果组件的数据有变化了(porp,state), 依次执行以下函数
• componentWillReceiveProps
• shouldComponentUpdate
• componentWillUpdate
• render
• componentDidUpdate
卸载时期的函数
销毁组件
•componentWillUnmount
1. import React,{ Component } from 'react'; 2. class Demo extends Component { 3. constructor(props) { 4. // 构造函数,要创造一个组件类的实例,会调用对应的构造函数, 5. //一个react组件需要构造函数,往往为了两个目的. 6. //1:初始化state.2.绑定成员函数this环境。 7. // 无状态的函数就不需要构造函数, 8. super(props) 9. console.log("---初始化组件---") 10. this.state = { 11. test:'想要显示一段不一样的文字' 12. //定义state,存放页面的数据,通过this.setState()方法修改 13. //.this.setState()函数所做的事情,首先是改变this.state的值,然后驱动组件经历更新过程,这样才有机会让this.state里新的值出现在界面上。 14. } 15. } 16. componentWillMount () { 17. console.log("---组件挂载前---最先执行的函数") 18. } 19. componentDidMount () { 20. console.log("---组件挂载后---") 21. } 22. componentWillReceiveProps (nextProps) { 23. console.log("---父组件重新渲染---") 24. 值得注意的是,更新时期的componentWillReceiveProps函数, 25. 只要父组件的render函数被调用,在render函数里面被渲染的子组件就会经历更新过程,不管父组件传给子组件的Props有没有改变,都会触发子组件的componentWillReceiveProps函数,但是自身的this.setState方法触发的更新过程不会调用这个函数。 26. } 27. shouldComponentUpdate (nextProps,nextState) { 28. console.log("---组件接受到重绘状态---") 29. 它决定了一个组件什么时候不渲染。 30. 在更新过程中shouldComponemtUpdata 返回 false那就立刻停止更新。 31. this.setState函数后会执行shouldComponemtUpdata 然后在决定我要不要更新。 32. 相反 shouldComponemtUpdata 函数返回 TRUE,接下来就会依次调用 33. componentWillUpdata,render,componetDidUpdata函数,它把render像夹心面包似得夹在了中间。 34. } 35. componentWillUpdate (nextProps,nextState) { 36. console.log("---组件将要更新---") 37. } 38. componentDidUpdate (prevProps,prevState) { 39. console.log("---组件更新完毕---") 40. } 41. render () { 42. console.log("---组件渲染---") 43. return ( 44. <div>{this.state.test}</div> 45. ) 46. } 47. componentWillUnmount () { 48. console.log("---组件销毁---") 49. } 50. } 51. export default Demo;
componentWillMount 和componentDidMount的区别:componentWillMount 可以在服务器调用,也可以在浏览器调用但是componentDidMount只能在浏览器被调用,因为装载是一个组件放到DOM树上的过程,那么真正的装载是不可能在服务器上完成的,服务器的渲染并不会产生DOM树。所以我们可以利用这一点。在componentDidMount被调用时候,组件已经被装载到DOM树上了,可放心的去操作渲染出来的任何DOM。
编写组件:
组件间的传递通过props进行传递,看下面例子
import React from 'react'; // 一级父组件 class Level1 extends React.Component{ render(){ return <Level2 color='red'/> } } // 二级子组件 class Level2 extends React.Component{ render(){ return <Level3 color={this.props.color}/> } } // 三级孙子组件 class Level3 extends React.Component{ render(){ return <div color={{color: this.props.color}}/> } }
也可以这样创建
-
import React from 'react'; const Level1 = React.createClass({ render() { return ( <div></div> ); } }); export default Level1 ;
React.createClass和extends Component的区别:
Component{}是ES6的写法,会自动继承Component里面的属性 createClass({})是React的ES5写法,会生成一个React Component 语法区别
• propType 和 getDefaultProps
• 状态的区别
• this区别
• Mixins
参考这篇文章
[React.createClass和extends Component的区别]
(https://segmentfault.com/a/1190000005863630)
如果你的组件是无状态的,纯输出组件也可以直接写成函数如下
-
function Pure(props){ return( <div>{props.xxxx}</div> ) }
react 组件必须一级一级传递 ,如果想要越级传递,1直接到5,那么需要用到redux
redux
redux之前最好了解下flux,但是redux更加优秀。 react和redux事实上是两个独立的东西,如果你两者单独使用推荐react-redux库,我们从redux 使用方法开始,循序渐进过渡到react-redux,这个库可以让我们简化代码的书写
在redux框架下,一个react组件是这样运行的
读取Store的状态,用于初始化组件的状态,同时监听Store的状态改变,当Store状态发生改变时候,就需要更新组件的状态,从而驱动渲染。当需要更新store状态时,就要派发action对象。 根据当前props,和state,渲染用户界面。
项目结构
actions--->用户行为 components--->组件 containers--->容器 reducer--->一个纯函数返回新的state状态 store--> store里面负责分发action行为 index.html ---> 模板文件 webpack---> 打包配置文件
actions:
• 是一个行为的抽象
• 是普通JS对象
• 一般由方法生成
• 必须有一个type 我要添加一本书这个行为可以如下:
-
const addTodo = (text) =>{ retrun { type:'Add', id: nextTodoId++, text, } }
reducer:
• 是响应的抽象
• 是纯方法
• 传入旧的状态和action
• 返回新的状态
签名函数:reducer(state, action) state 是当前状态,action是接受到的action, 注意不能改变参数state和action
-
const todo = (state, action) =>{ switch (action.type){ case "Add_Book": return { text: action.text, } }
用一个例子串起来:
设计一个具有加减功能的项目:
Actions.js:
export const increment = (counterCaption) => { return { type: increment, counterCaption: counterCaption }; }; export const decrement = (counterCaption) => { return { type: decrement, counterCaption,//es6写法等同于counterCaption: counterCaption }; }; Reducer.js: export default (state, action) => { const {counterCaption} = action;//等同于const counterCaption= action.counterCaption; switch (action.type) { case increment: return {...state, [counterCaption]: state[counterCaption] + 1}; case decrement: return {...state, [counterCaption]: state[counterCaption] - 1}; default: return state } } //return {...state, [counterCaption]: state[counterCaption] - 1};等同于 //const newState = Object.assign({},state); //newState[counterCaption]--; //return newState;
Store.js:
import {createStore} from 'redux'; import reducer from './Reducer.js'; const initValues = { 'First': 0, 'Second': 10, 'Third': 20 }; const store = createStore(reducer, initValues); export default store; //createStore是redux库提供的函数第一个参数是更新状态的reducer,第二参数是初始值
views(容器):
import React, { Component } from 'react'; import Counter from './Counter.js'; class ControlPanel extends Component { render() { return ( <div> <Counter caption="First" /> <Counter caption="Second" /> <Counter caption="Third" /> </div> ); } } export default ControlPanel;
Counter.js(组件):
import React, { Component, PropTypes } from 'react'; import store from '../Store.js'; import * as Actions from '../Actions.js'; const buttonStyle = { margin: '10px' }; class Counter extends Component { render() { const {caption, onIncrement, onDecrement, value} = this.props; return ( <div> <button style={buttonStyle} onClick={onIncrement}>+</button> <button style={buttonStyle} onClick={onDecrement}>-</button> <span>{caption} count: {value}</span> </div> ); } } //以下是对参数类型的定义,开启eslint需要写一下代码。 Counter.propTypes = { caption: PropTypes.string.isRequired,//表示caption是string类型,必填 onIncrement: PropTypes.func.isRequired, onDecrement: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; class CounterContainer extends Component { constructor(props) { super(props); this.onIncrement = this.onIncrement.bind(this); this.onDecrement = this.onDecrement.bind(this); this.onChange = this.onChange.bind(this); this.getOwnState = this.getOwnState.bind(this); this.state = this.getOwnState(); } getOwnState() { return { value: store.getState()[this.props.caption] }; } onIncrement() { store.dispatch(Actions.increment(this.props.caption)); } onDecrement() { store.dispatch(Actions.decrement(this.props.caption)); } onChange() { //为了保持Store上的状态和this.state的同步 this.setState(this.getOwnState()); } shouldComponentUpdate(nextProps, nextState) { return (nextProps.caption !== this.props.caption) || (nextState.value !== this.state.value); } componentDidMount() { //为了保持Store上的状态和this.state的同步 store.subscribe(this.onChange); } componentWillUnmount() { //为了保持Store上的状态和this.state的同步 store.unsubscribe(this.onChange); } render() { //Counter 在上面 return <Counter caption={this.props.caption} onIncrement={this.onIncrement} onDecrement={this.onDecrement} value={this.state.value} /> } } CounterContainer.propTypes = { caption: PropTypes.string.isRequired }; export default CounterContainer;
通常我们会把容器放在container文件夹下,把组件放在component下
ControlPanel 根本就没有使用store,如果仅仅为了传递prop给组件counter就要求支持state prop,显然不合理,其中react提供了Context的功能可以解决这个问题;
Context:
我们增加Provider.js,代码如下:
import {PropTypes, Component} from 'react'; class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; //Provider包裹的子元素输出出来 } } Provider.contextTypes = { store: PropTypes.object } export default Provider;
index.js 文件引入Provider
import React from 'react'; import ReactDOM from 'react-dom'; import ControlPanel from './views/ControlPanel'; import store from './Store.js'; import Provider from './Provider.js'; ReactDOM.render( <Provider store={store}> <ControlPanel /> </Provider>, document.getElementById('root') );
最后我们在修改ControlPanel中的Counter组件,
React-Redux:
如果理解上面的例子之后你会发现有些复用的部分可以提取出来,各个组件关心自己的部分就行了,react-redux库就是解决这个事情的,让你开发爽到飞起
react-redux 规定,所有的 UI 组件都由用户提供,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
首先我们先了解一下重要的函数connect,React-Redux 提供connect方法,用于从 UI 组件生成容器组件,就是将这两种组件连起来。 connect方法的完整 API
import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList)
connect方法接受两个参数:mapStateToProps和mapDispatchToProps。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
此时index.js文件变成:
import React from 'react'; import ReactDOM from 'react-dom'; import {Provider} from 'react-redux'; import ControlPanel from './views/ControlPanel'; import store from './Store.js'; ReactDOM.render( <Provider store={store}> <ControlPanel/> </Provider>, document.getElementById('root') ); //Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了
Counter.js文件变成
import React, { PropTypes } from 'react'; import * as Actions from '../Actions.js'; import {connect} from 'react-redux'; const buttonStyle = { margin: '10px' }; function Counter({caption, onIncrement, onDecrement, value}) { return ( <div> <button style={buttonStyle} onClick={onIncrement}>+</button> <button style={buttonStyle} onClick={onDecrement}>-</button> <span>{caption} count: {value}</span> </div> ); } Counter.propTypes = { caption: PropTypes.string.isRequired, onIncrement: PropTypes.func.isRequired, onDecrement: PropTypes.func.isRequired, value: PropTypes.number.isRequired }; function mapStateToProps(state, ownProps) { return { value: state[ownProps.caption] } } function mapDispatchToProps(dispatch, ownProps) { return { onIncrement: () => { dispatch(Actions.increment(ownProps.caption)); }, onDecrement: () => { dispatch(Actions.decrement(ownProps.caption)); } } } export default connect(mapStateToProps, mapDispatchToProps)(Counter);
connect函数实际上是个高阶函数,了解可以参考这边文章
[Higher-Order Components](https://reactjs.org/docs/higher-order-comonents.html)
关于react 路由可以参考这边文章
[路由](http://www.ruanyifeng.com/blog/2016/05/react_router.html?utm_source=tool.lu)