setState
- 使用 setState会将当前和先前的状态合并。replaceState会移除当前状态,仅使用新提供的内容替换它。
- setState
- 首选方法是使用函数而不是对象调用 setState。函数将前一个状态作为第一个参数,当前时刻的 props 作为第二个参数。React 可能将多个 setState调用合并成单个进行更新,所以当状态的更新值依赖最新的state和props时使用对象形式的更新会有问题。
- 即使state没有改变只要触发setState就会重新渲染。setState在生命周期中执行以及在react事件中执行都是异步执行的,但是在setTimeout中和原生事件中是同步执行的(有一个变量isBatchingUpdates默认为false,在生命周期中或者事件触发中为true,执行完之后改为false,改参数决定setState是否为异步)。
constructor
- constructor中在调用super()之前子类的构造函数中没有this的引用,无法使用this,super(props)将props传递之后可以在constructor中使用this.props来获取传入的props。
- 在不使用props作为状态时,可以不写constructor方法,直接state={}即可。
react事件处理
在 React 中不能使用返回 false 的方式阻止默认行为。必须明确的使用 preventDefault。
绑定this
react中类方法需要绑定到类实例,因为this会根据当前的执行上下文改变,而我们使用方法希望this引用的是当前的实例,所以需要绑定到当前实例。
- constructor中绑定
- 定义函数使用箭头函数
onClick={this.a.bind(this)}onClick={() => { this.a() }}
事件传参
- 通过bind方法传参事件对象可以隐式传递,事件对象 e 要排在所传递参数的后面。
a = (id, e) => {
console.log(id, e.target.value);
}
onClick={this.a.bind(this, id)}
- 通过箭头函数传参事件对象必须显式传递
a = (id, e) => {
console.log(id, e.target.value);
}
onClick={e=> {this.a(id ,e)}}
- react使用事件对象时如果不用传递参数可以直接使用
open = e => {
console.log(e.target);
}
<button onClick={this.open}>button</button>
在事件中直接触发一个onClick事件
onSelect = () => {
// ...
document.body.click(); // 不是onClick
}
react-router
- connect(mapStateToProps, mapDispatchToProps, null, { pure:false }) redux加router不能监听路由变化时加最后一个参数可以监听到路由变化。
router实现方法
- 利用 hashchange 事件来监听 url 的 hash 变化,其兼容性较好。但是url的形式与常规url不太相同。
HashRouter的URL带有#号,可以刷新,获取URL的路由参数使用location.hash。
- 调用 history 的 H5 API(pushState, replaceState等)能够在不刷新页面的情况下改变 url,利用 popstate 事件可以监听到这种改变,兼容性略差,但是url风格可以与服务端基本保持一致,且可以隐性的传参,目前使用此类路由较多。
BrowserRouter的URL中没有#,但是需要服务端配置,且不可以刷新(在index.html中修改 .js 引入路径,使用绝对地址进行引入就可以刷新
<script src="/bundle.js"></script>或在webpack中配置 ),获取URL的路由参数使用location.pathname。
react-router4
使用react-router-dom库即可,相较于 react-router 的区别是多了很多 DOM 类组件(如
<Link> <BrowserRouter>等);import { HashRouter, Router, Link, Route } from 'react-router-dom'时使用<HashRouter>或<BrowserRouter>;不同于import { HashRouter as Router, Link, Route} from 'react-router-dom'直接使用<Router>,引入HashRouter但是作为Router引入的,所以使用Router;
- Router在全局中只能有一个,Router不能嵌套Router,否则会有很多问题;Switch可以嵌套Switch。
- Router组件下仅能包含一个子节点,必须将 Route 包装在Switch中,因为Switch只渲染第一个匹配的路由。
- react-router的push方法将添加一个新的历史记录,replace方法只是覆盖当前的地址,不会添加新的历史记录。
<Link>
导航组件用于路由跳转,使用HashRouter时点击同一路由会报错
<li><NavLink to="/RoutingNested/2" replace>示例2</NavLink></li>加上replace即可,BrowserRouter不会有此情况。
- to: 参数为字符串时,直接跳转到指定路径。参数为对象时,可以传递以下字段:pathname: 页面路径; search: 页面 url 参数; hash: window.location.hash; state: 隐性传递的一些参数。
- replace: bool 如果为true时, 不会将新的地址压入历史栈,会覆盖原来的历史记录。
<NavLink>是<Link> 的一个特定版本, 会在匹配上当前 URL 的时候会给已经渲染的元素添加样式参数
- activeClassName: string 选中时类名。
- activeStyle: object 选中时的行内样式。
- exact: bool 若为true,则严格匹配路由,类似 的exact属性。
- strict: bool 若为true,则严格匹配“/”,类似 的strict属性。
- isActive: func 此属性可以添加额外的逻辑来确定路由是否激活,如果除了验证链接与当前 URL 匹配之外,还想执行更多操作,则可以使用这个属性。
// activeClassName选中时类为selected
<NavLink to="/1" activeClassName="selected">1</NavLink>
// 选中时样式为activeStyle的样式设置
<NavLink
to="/1"
activeStyle={{
fontWeight: 'bold',
color: 'red'
}}
>1</NavLink>
Route
访问的地址与 Route 上的路径匹配时,渲染出对应的 UI 页面。默认会有三个参数:match、location、history。
// component的形式
<Route path="/news" component={News} />
// render 函数的形式,可以添加自定义的props
<Route path="/home" render={() => <div>Home</div>} />
// 渲染一个组件,可以自定义添加props,但是不会有默认的原来的路由组件的props(location、history、match),可以在将外层的props传入,可以将props解构传入。
<Route path="/home" render={props => <Exer {...props} type="add" />} />
// children与 render 类似,也是传入需要返回一个 React 元素的函数,区别是不管路径是否匹配,传入的 children 都会渲染。
<Route path="/home" children={() => <div>Home</div>} />
- exact
排它性(exact)路由和包容性路由,exact 如果我们删除 exact 这个 props,那么我们在访问 /user 的时候,Home 和 User 两个 Page 都会被渲染。
<Route path="/" exact component={HomePage} />
<Route path="/user" component={UsersPage} />
- strict: bool
当 strict 为 true 的时候,路由后面的斜杠将被严格匹配,如“/one/”无法匹配“/one”,但是可以匹配“/one/”或者“/one/two”。反之亦然。
- sensitive: bool
当 sensitive 为 true 的时候,会对大小写敏感,如“/One”无法匹配“/one”。反之亦然。
Switch
Switch 组件包裹 Route 和 Redirect组件,将会渲染第一个匹配路由的组件。 在Switch中只有一个路由规则被应用,一旦匹配上就不会继续往下匹配,跟书写的顺序有关。Switch内部还可以再嵌套Switch;
<Redirect>
将会重定向到一个新的地址,并且新地址覆盖现有地址在访问历史里的记录,无法回退至现有地址。可以用于判断是否登录然后跳转到登录页面。或者设置默认页面等。
- to: string | object 重定向的地址
- push: bool 若为 true,将会把地址推入历史记录中,而不是替换现有地址。
- from: string 需要匹配的现有地址。
- exact: bool 若为true,则严格匹配路由,类似 的exact属性。
- strict: bool 若为true,则严格匹配“/”,类似 的strict属性。
路由跳转
路由的跳转可以指定多个参数,比如pathname和search等,以对象的形式传入 this.props.history.push({ pathname: '/path', search: location.search })
this.props.history.push('/path') // 在非路由根组件中不能使用,因为props没有history,可以使用withRouter高阶组件将要使用此方法的组件包裹一下。export default withRouter(A)
// 在没有history的情况下而且不想使用withRouter包裹组件可以自己创建一个history
// history.js
import createHistory from 'history/createBrowserHistory';
export default createHistory();
// Router组件
import history from './history';
<Router history={history}>
...
</Router>
// UI组件
import history from './history';
history.push('/home')
Route直接对应的路由组件默认会有一些额外的props(location、history、match三个属性),普通组件使用withRouter包裹就会有
将普通组件使用withRouter包裹之后。组件会有location、history、match三个属性,如果已经是路由组件则默认会有该属性(Route直接匹配到的组件)。同时可以使用this.props.history.push('/path')方法。
import { withRouter } from "react-router-dom";
export default withRouter(MyComponent);
设置错误路由路径时的页面
// 路由规则写在最后
<Route path="*" component={NotFound}/>
嵌套路由
- 不是根路由组件中有嵌套路由的时候,一定要将组件的props传递到内层组件才可以正常使用(Tab下的子组件的slidePanel形式)或者使用withRouter包裹。
react-router3
打开页面的默认页面(用于嵌套路由)
<IndexRedirect to="/home" /> // 打开直接跳转到该路由(url路径变化),且路由的to的值不要使用index
<IndexRoute component={List}/> // 默认显示组件,url的路径不变
生命周期
- componentWillMount(不建议使用) componentDidMount 这两个生命周期函数之间会有render函数触发,组件一次挂载只会触发一次,componentDidMount多用于数据的初始化请求。可以在componentDidMount中建立定时器。对于依赖 DOM 的操作,可以把这些操作放在 componentDidMount 当中。
- componentWillReceiveProps(nextProps)(不建议使用) 组件render期间中的props的改变会触发此生命周期函数。
- shouldComponentUpdate(nextProps, nextState)(不建议使用) 返回一个布尔值,false就不会重新render,可以在你确认不需要render组件时使用。
- componentWillUpdate(nextProps, nextState)(不建议使用) componentDidUpdate(prevProps, prevState)这两个生命周期函数之间会有render函数触发,will时state还未改变,did时state已经改变。
- componentWillUnmount: 一般用于全局事件(该事件只作用于当前组件)的清除如onscroll(会先触发父组件的componentWillUnmount然后触发子组件componentWillUnmount),也会在该钩子函数中清除定时器。
- render函数也是生命周期函数,render渲染的为虚拟DOM,视图改变前后两次虚拟DOM会进行比较,进行diff从而更新真实DOM。
- static getDerivedStateFromProps(nextProps, prevState) 这个生命周期函数是为了替代componentWillReceiveProps,该函数会在组件实例化以及接收新props后调用(state变化也会调用re-render都会调用)。它可以返回一个对象来更新state,或者返回null来表示新的props不需要任何state更新。函数会在每一次re-render之前调用,即使props没有改变,setState导致state改变,该函数依然会被调用,getDerivedStateFromProps是一个静态函数,也就是这个函数不能通过this访问到class的属性,不推荐直接访问属性。而是应该通过参数提供的nextProps以及prevState来进行判断,根据新传入的props来映射到state。如果props传入的内容不需要影响到你的state,那么就需要返回一个null,这个返回值是必须的。
static getDerivedStateFromProps(nextProps, prevState) {
const { type } = nextProps;
// 当传入的type发生变化的时候,更新state
if (type !== prevState.type) {
return {
type,
};
}
// 否则,对于state不进行任何操作
return null;
}
- getSnapshotBeforeUpdate(prevProps, prevState)此生命周期的返回值将作为第三个参数传递给componentDidUpdate生命周期。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>
{/* ...contents... */}
</div>
);
}
}
- componentDidCatch(error, info) 处理错误的钩子函数
记录错误并显示回退 UI 而不是页面崩溃。如果一个类组件定义了一个名为 componentDidCatch(error, info) 或 static getDerivedStateFromError() 新的生命周期方法,则该类组件将成为错误边界。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
logErrorToMyService(error, info)
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
render() {
// 即使有错误也不会页面崩掉
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>{'Something went wrong.'}</h1>
}
return this.props.children
}
}
// 将其作为常规组件使用
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
新的生命周期顺序
- 创建期
- constructor(构造函数只会执行一次, url上的参数放在state时要谨慎使用)
- static getDerivedStateFromProps
- render
- componentDidMount
- 存在期(re-render)
- static getDerivedStateFromProps
- shouldComponentUpdate
- getSnapshotBeforeUpdate
- render
- componentDidUpdate
- 销毁期
- componentWillUnmount
函数组件
声明变量的问题
- 在函数组件外部定义变量,修改变量值之后组件销毁然后重新挂载,变量的值会保持修改后的值,而不是初始值,但是在父组件中调用不到此变量。
- 在函数组件内部定义变量,只要组件进行更新,此变量的值就会重新进行初始的赋值
ref
如果一个元素为display:none该元素使用ref获取不到。
设置ref的方法
- ref=string
<input type="text" className="form-control" ref="name"/>
handleSubmit = () => {
let name = this.refs.name.value;
console.log(name);
}
- React.createRef()创建refs,可以使用ref的current属性节点的引用进行访问。
class CustomTextInput extends React.Component {
constructor(props) {
super(props);
// 创建 ref 存储 textInput DOM 元素
this.textInput = React.createRef();
}
focusTextInput = () => {
// 通过 "current" 取得 DOM 节点
this.textInput.current.focus();
}
render() {
return (
<div>
<input type="text" ref={this.textInput} />
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
- 回调函数的方式 官方推荐使用
如果将 ref 回调定义为内联函数,则在更新期间它将会被调用两次。首先使用 null 值,然后再使用 DOM 元素。这是因为每次渲染的时候都会创建一个新的函数实例,因此 React 必须清除旧的 ref 并设置新的 ref。建议使用外部函数赋值给ref。
- 组件被挂载后,回调函数会立即执行,回调函数的参数为该组件的具体实例;
- 组件卸载或者原来的ref属性本身发生变化的时候,回调函数也会被立即执行,此时回调函数参数为null,以确保内存不泄漏。
<input type="text" ref={ref => this.name = ref} />
handleSubmit = () => {
const name = this.name.value;
console.log(name);
}
// 另一种写法
handleSubmit=()=>{
const name = this.name.value;
console.log(name);
}
bindRef = ref => this.name = ref
<input type="text" ref={this.bindRef} />
ref可以绑定另一个组件或者另一个组件的元素
绑定另一个组件可以在组件外部使用组件内部的方法,使用ref获取该组件然后.方法名,调用子组件实现校验子组件(field的传递)。或者绑定另一个组件的元素,对元素进行操作。
// 外层组件
bindRef = ref => {
this.comChild = ref;
}
// 使用内部组件的方法
this.comChild.onSubmit();
<ChildCom bindRef={this.bindRef}>
// 内层组件的DidMount函数或者constructor中绑定this(必须有)
this.props.bindRef && this.props.bindRef(this)
函数组件中也可以使用ref
函数组件虽然没有this,但是可以使用一个变量代替。
const FuncCom = () =>
let refele;
return (
<div>
<div ref={input => refele = input}>dyx</div>
</div>
)
}
redux 中间件
中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展。中间件会对特定类型action做一定的转换,但是最后传给reducer的action一定是标准的纯对象。
redux-thunk
redux-thunk中间件可以让action创建函数先不返回一个action对象,而是返回一个函数,函数传递两个参数(dispatch,getState),在函数体内进行业务逻辑的封装,改造store.dispatch,使得后者可以接受函数作为参数。 中间件收到action后会执行action方法并将结果提供给reducer。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
applyMiddleware(thunk)
);
// Action Creator
import { message } from 'antd';
export const getUserInfo = () => {
//返回了一个函数,而普通的 Action Creator 默认返回一个对象。
return dispatch => {
return fetch('http://localhost:8080/user.json').then(response => {
return response.json();
}).then(json => {
// 这里dispatch的是常规的action用于改变state
dispatch(getUserInfoSuccess(json));
message.success('成功');
}).catch(() => {
dispatch(getUserInfoFail());
message.error('成功');
})
}
}
import { getUserInfo } from "actions/userInfo";
<button onClick={() => this.props.dispatch(getUserInfo())}>请求用户信息</button>
css Module
- CSSModules的装饰器写在最下面(connect, withRouter在前)
// css
.container {
:global {
.ant-tabs-tabpane {
padding-left: 20px;
}
.dyx {
color: red;
}
}
}
// js
import styles from './index.less';
<div className={styles.container}> // css module形式
<Tabs defaultActiveKey="normal" />
<div className="dyx">123</div> // 非 css module形式
</div>
key
react元素的key应该是稳定和唯一的,这样 React 就能够跟踪元素。不建议使用循环的index作为key值,如果元素个数发生变化,react可能需要重新创建元素,这将限制 React 能够实现的优化。
- 可以使用索引作为key的条件
- 列表项是静态的,它们不会被计算,也不会更改。
- 列表中的列表项没有 ids 属性。
- 列表不会被重新排序或筛选。
- 数组循环中使用的key在其同级中应该是唯一的,但它们不需要是全局唯一的。可以在两个不同的数组循环中使用相同的键。
- Fragment不支持key属性
Render Props
组件的props名字为render,值为一个函数,是组件用来了解渲染什么内容的props,该函数会返回一些渲染的结果,从而保证该组件的内容得到动态渲染。
// 定义render props,作用是为了更好地复用Mouse组件,详例见官网
<Mouse render=(mouse => (
<Cat mouse={mouse}>
))>
// 使用,在Mouse组件中传入一个值,会根据传入的值,渲染render props的内容
{this.props.render(this.state)}
Portals
Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
ReactDOM.createPortal(child, container)
context
props只能逐级传递数据,使用context可以实现跨级传递数据,Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。context可以被所有的后代组件使用,一定要有类型检验。
老版本的API
// 父组件,最上层的组件
import React, { Component,PropTypes } from 'react';
import Son from './Son';
class Father extends Component {
getChildContext() {
return {test: "hello"};
}
render() {
return (
<div>
<p>父组件</p>
<Son/>
</div>
);
}
}
Father.childContextTypes = {
test: PropTypes.string
};
export default Father;
// 子组件,即使子组件不使用context,后代组件依然可以使用
import React, { Component, PropTypes } from 'react';
import Grandson from './Grandson';
class Son extends Component {
render() {
console.log('this.context', this.context);
return (
<div>
<p>子组件,获取父组件的值:{this.context.test}</p>
<Grandson/>
</div>
);
}
}
Son.contextTypes = {
test: PropTypes.string
};
export default Son;
// 孙组件
import React, { Component, PropTypes } from 'react';
class Grandchild extends Component {
render() {
console.log('this.context:',this.context);
return (
<div>
<p>孙组件,获取传递下来的值:{this.context.test}</p>
</div>
);
}
}
Grandchild.contextTypes = {
test: PropTypes.string
};
export default Grandchild;
在react中使用immutable
在react中改变一个有很多项的对象的某一个值,会用到object.assgin,会从旧的对象中浅拷贝到新的对象,会浪费性能,使用immutable会节省很多性能。
// 初始化状态
import Immutable from 'seamless-immutable';
state = {
orderList: Immutable([]),
}
// 改变状态
saveOrderList = (state, { payload: items }) => {
return {
...state,
orderList: Immutable(items)
};
}
使用pure-render-decorator,修饰器为ES7属性需要babel配置
import React from 'react';
import pureRender from 'pure-render-decorator';
@pureRender
class OrderListView extends React.Component {
render() {
const { orderList } = this.props;
return (
<div>
{
orderList.map((item) => {
return (
<div key={item.orderNum}>
<div>{item.orderNum}</div>
</div>
);
})
}
</div>
);
}
}
export default OrderListView;
组件测试
- 组件测试中有获取dom节点的需求时使用 mount(, { attachTo: window.domNode }) 的形式 stackoverflow.com/questions/4…
细节记录
- react中使用label标签的for属性应使用 htmlFor 来替代,因为 for 是 JavaScript 的保留字。
- react中使用dangerouslySetInnerHTML向html标签中插html字符串。
<div dangerouslySetInnerHTML={{ __html: '<h1>aaa</h1>' }} />
- 使用iconfont 选择 Font class 的css代码,在index.html中引入后直接使用className的形式即可 iconfont 以及 icon-type两个样式即可。
- 组件根据不同props渲染不同的html元素或者不同的UI组件可以使用React.createElement(component, props, ...children) 方法。
- switching 组件是渲染多个组件之一的组件。基于 page 属性显示不同的页面,最后渲染的组件变量首字母必须大写。
import HomePage from './HomePage';
import AboutPage from './AboutPage';
import ServicesPage from './ServicesPage';
import ContactPage from './ContactPage';
const PAGES = {
home: HomePage,
about: AboutPage,
services: ServicesPage,
contact: ContactPage
};
const Page = (props) => {
const Handler = PAGES[props.page] || ContactPage
return <Handler {...props} />
}
Page.propTypes = {
page: PropTypes.oneOf(Object.keys(PAGES)).isRequired
}