import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; class App extends React.Component { state = { title: '环球大前端' } render() { const { title } = this.state; const { name } = this.props return ( <div> <h2>{title}</h2> <p> Hello {name}! </p> </div> ) } } App.propTypes = { name: PropTypes.string } App.defaultProps = { name: '帅气小伙子' } ReactDOM.render(<App name="24小清新" />, document.getElementById('app'));
var App = React.createClass({ getDefaultProps: function() { return { name: '帅气小伙子' } }, getInitialState: function() { return { title: '环球大前端' } }, render: function() { return ( <div> <h2>{this.state.title}</h2> <p> Hello {this.props.name}! </p> </div> ) } }) React.render(<App name="24小清新" />, document.getElementById('app'));
核心思想:封装组件,各个组件维护本身的状态(state, prop)和UI,当状态变动,自动从新渲染组件,数据流向是单向的。javascript
须要明白的几个基础概念:html
一、什么是JSX?前端
二、如何修改组件state,从而修改组件UI?java
三、事件处理react
对于上述那些既不是字符串也不是 HTML的的标签语法,被称为JSX,是一种 JavaScript 的语法扩展,用来描述用户界面。es6
经常使用的是在JSX中使用表达式,例如 2 + 2, user.firstName, 以及 formatName(user),条件判断(三目运算符、&&), 数组Map函数遍历获取React元素 都是可使用的。如:ajax
formatName(user) { return `${user.firstName}-${user.name}`; } const user = { firstName: 'wu', name: 'shaobin' } const show = true; //我能够是this.state属性哦!!! const arr = ['xiaobin', 'kaizi', 'liujun']; const element = ( <div> <h1>Hello, {formatName(user)}!</h1> <h1>Hello, {user.name}!</h1> <h1>Hello, { 1 + 1 }!</h1> <h1>Hello, { show ? 'I am show' : null }</h1> <h1>Hello, { arr.length > 0 && <span>数组长度大于0</span> }</h1> { arr.map((item, index) => { return <span key={item}>item</span> }) } //记住数组Map函数遍历获取React元素的时候,必需要记得必须➕keys属性 // 为啥呀? //Keys能够在DOM中的某些元素被增长或删除的时候帮助React识别哪些元素发生了变化。所以你应当给数组中的每个元素赋予一个肯定的标识。没有惟一值的时候可使用index,可是官方不建议,会致使渲染变慢。 </div> ); ReactDOM.render( element, document.getElementById('root') );
切记每一个组件的render函数返回的JSX结构都须要根元素去包裹着,固然也有例外,如React.Fragmentredux
对于JSX,react最终经babel的转换会调用React.createElement相应api转换成react能识别的对象,如上述例子转换后获得:api
React.createElement( 'div', null, React.createElement( 'h2', //能够是一个html标签名称字符串,也能够是也能够是一个 React component 类型 null, title ), React.createElement( 'p', null, ' Hello ', name, '! ' ) );
既然咱们能够为组件初始化状态,也必需要可以去改变它,以达到改变视图。
固然this.state.xxx = xxx
不会触发渲染组件的动做,而是使用this.setState({ xxx: xxx })
方法来修改状态,同时多个setState() 调用合并成一个调用能提升性能。
对于事件处理,须要注意的一点就是this的绑定,其余跟普通Dom绑定监听事件同样,this的绑定有如下几种方式:
同时须要注意的是,React合成事件(onClick={})和原生事件(document.addEventListener)的阻止冒泡存在差别,须要明白的是,全部合成事件都是绑定在document上的(代理),因此执行合成事件中的event.stopPropagation(),实际原生事件仍是会冒泡到document上,同时须要注意的是,setState 只在合成事件和生命周期函数中是 "异步" 的,在原生事件和 setTimeout 中都是同步的。
合成事件与原生事件列子
参考文章:
从 Dropdown 的 React 实现中学习到的
React合成事件和DOM原生事件混用须知
一、有哪些生命周期,生命周期的执行顺序?
二、Ref的引用
三、高阶组件的使用
constructor() //React组件的构造函数,用于super(props),初始化state,bind绑定事件等
static getDerivedStateFromProps()
UNSAFE_componentWillMount() // 组件挂载前(组件渲染到页面前)
render() // 渲染函数,不作实际的渲染动做,它只是返回一个JSX描述的结构,生成虚拟Dom树,执行patch,最终由React来操做渲染过程
componentDidMount() //组件挂载后(组件渲染到页面上),能够在这个钩子添加异步请求以及定时器,或是socket链接
UNSAFE_componentWillReceiveProps() // 组件接收到属性时触发
static getDerivedStateFromProps()
shouldComponentUpdate(prevProps, prevState) // 当组件接收到新属性,或者组件的状态发生改变时触发。组件首次渲染时并不会触发,此钩子可作性能优化
UNSAFE_componentWillUpdate() //组件即将被更新时触发
render() // 渲染函数,不作实际的渲染动做,它只是返回一个JSX描述的结构,生成虚拟新Dom树,与旧树进行diff, 执行patch
getSnapshotBeforeUpdate()
componentDidUpdate(prevProps, prevState, snapshot) // 组件被更新完成后触发,生命周期中因为state的变化触发请求,在componentDidUpdate中进行
componentWillUnmount() // 卸载组件,注销监听事件或是定时器,socket关闭
详细看生命周期例子
getSnapshotBeforeUpdate()与static getDerivedStateFromProps()两个新增生命周期钩子是被用来代替 UNSAFE_componentWillMount() ,UNSAFE_componentWillUpdate(), UNSAFE_componentWillReceiveProps()三个生命周期的,可是这个三个生命周期还是可使用。为何勒?React为了1.7版本实现Async Rendering。
Refs 提供了一种方式,用于访问在 render 方法中建立的 DOM 节点或 React 元素,官方建议少用。获取Ref有三种场景:
获取Ref的经常使用方式(经过this.myRef.current来获取Dom节点或实例):
class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); // 调用React.createRef API } render() { return <input ref={this.myRef} />; } }
class MyComponent extends React.Component { constructor(props) { super(props); } render() { return <input ref={(ref) => { this.myRef = ref; }} />; } }
Ref获取Dom元素例子, Ref获取React元素(子组件实例)例子。
补充Tip:
对于第三种状况,获取子组件的Dom节点,官方有提供Forwarding Refs(转发Ref)的方法,来获取子组件的Dom节点的Ref,此方法返回的是一个React元素,对应方法为React.forwardRef((props, ref) => { ... })
高阶组件(HOC)是react中对组件逻辑进行重用的高级技术。但高阶组件自己并非React API。它只是一种模式,其实就是一个函数,且该函数接受一个组件做为参数,并返回一个新的组件。高阶组件在React第三方库中很常见,好比Redux的connect方法和react-router的withRouter()方法。
注意事项:
一、不要再render函数中使用高阶组件,否则会致使每次从新渲染,都会从新建立高阶组件实例,销毁掉旧的高阶组件,致使全部状态和子组件都被卸载。
二、必须将静态方法作拷贝,当使用高阶组件包装组件,原始组件被容器组件包裹,获得新组件会丢失原始组件的全部静态方法,假如原始组件有静态方法,可使用hoist-non-react-statics进行静态方法拷贝。例子
三、Refs属性不能传递,高阶组件能够传递全部的props属性给包裹的组件,可是不能传递refs引用,可是有时候咱们确实须要把ref的引用传给包裹组件,能够传一个非ref命名的props属性给到高阶组件上,由高阶组件绑定到包裹组件的ref上,也可使用转发Ref。例子
Redux主要分红三部分,分别为Store,Action,Reducer,下面是对三部分的通俗的讲解:
Store:Redux应用只有一个单一的Store,就是单一数据源,将整个应用共享的状态state储存在一棵对象树上面,注意的是,对象树上面的state是只读的,只能经过纯函数来执行修改,建立store,是经过Redux的 createStore(reducer)方法来建立的,store里面会有getState()、dispatch()、subscribe(listener)的方法。
Action:一个普通的Javascript对象,描述了应用state发生了什么变化,经过dispatch方法来通知store调用reducer方法。
Reducer:描述应用如何更新state,自己是一个函数,接受Action参数,返回新的state。
须要明白的一点Redux跟React一点关系都没有,可是React搭配Redux来实现状态管理时最好的实现方案。那么如何搭配呢?原本咱们能够subscribe(listener)在react的组件注册redux的监听器,可是这种方式繁琐,并且会致使屡次渲染。因此搭配着react-redux来使用。基本使用以下:
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import App from './components/App' const todoApp = (state = {}, action) { if (actions.type === 'SHOW') { return Object.assign({}, state, { show: action.show }); } return state; } let store = createStore(todoApp) render( // 使用指定的 React Redux 组件 <Provider> 来让全部容器组件均可以访问 store,而没必要显示地传递它。只须要在渲染根组件时使用便可。 <Provider store={store}> <App /> </Provider>, document.getElementById('root') )
import React from 'react' import { connect } from 'react-redux'; import Child from './components/Child' class App extends Component { render() { const { show } = this.props; return ( <div> <Child show={show }/> </div> ); } } const stateToProps = (state) => ({ show: state.show }); export default connect(stateToProps)(App);
react-redux内容实现原理,使用的Context API,简单来讲,父组件声明须要跨层级组件传递的属性(childContextType)以及监听属性变化的getChildContext()函数,子组件声明能够接收上层组件传递的属性(contextType)。
若是存在多个reducer的话,可使用redux中的combineReducers进行合并,代码以下:
import { createStore, combineReducers } from 'redux'; const todoApp = combineReducers({ reducer1, reducer2 }) const store = createStore(todoApp);
代码等价于:
import { createStore} from 'redux'; const todoApp = (state = {}, action) => { return { reducer1: state.reducer1, reducer2: state.reducer2 } } const store = createStore(todoApp);
combineReducers最终只是生成一个函数,这个函数来调用你的一系列 reducer,每一个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,而后这个生成的函数再将全部 reducer 的结果合并成一个大的对象。详细逻辑能够看redux源码。
对于路由规则,咱们在项目里面搭配的是react-router v4这个库来完成的,因为咱们这以前也没接触过react-router,因此版本v3与v4之间模式和策略的差别不一样也没有带来思惟模式转换的困难,下面先帖码简单看看v3与v4版本之间的差别性(摘自掘金):
import { Router, Route, IndexRoute } from 'react-router' const PrimaryLayout = props => ( <div className="primary-layout"> <header> Our React Router 3 App </header> <main> {props.children} </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <Router history={browserHistory}> <Route path="/" component={PrimaryLayout}> <IndexRoute component={HomePage} /> <Route path="/users" component={UsersPage} /> </Route> </Router> ) render(<App />, document.getElementById('root'))
import { BrowserRouter, Route } from 'react-router-dom' const PrimaryLayout = () => ( <div className="primary-layout"> <header> Our React Router 4 App </header> <main> <Route path="/" exact component={HomePage} /> <Route path="/users" component={UsersPage} /> </main> </div> ) const HomePage =() => <div>Home Page</div> const UsersPage = () => <div>Users Page</div> const App = () => ( <BrowserRouter> <PrimaryLayout /> </BrowserRouter> ) render(<App />, document.getElementById('root'))
差别性:
一、v3是集中性路由,全部路由都是集中在一个地方, 而v4则相反。
二、v3布局和页面嵌套是经过 组件的嵌套而来的,而v4不会互相嵌套
三、v3布局和页面组件是彻底纯粹的,它们是路由的一部分,而v4路由规则位于布局和 UI 自己之间
四、使用v4须要在咱们的组件根部用BrowserRouter组件(用于浏览器)去包装,实现原理与react-redux的Provider组件同样(Context API),以便组件能够去拿到路由信息。
这里主要介绍包容性路由、排他性路由、嵌套路由,以及withRouter的一些基本用法。