react 使用记录

728 阅读15分钟

setState

  • 使用 setState会将当前和先前的状态合并。replaceState会移除当前状态,仅使用新提供的内容替换它。
  • setState
    1. 首选方法是使用函数而不是对象调用 setState。函数将前一个状态作为第一个参数,当前时刻的 props 作为第二个参数。React 可能将多个 setState调用合并成单个进行更新,所以当状态的更新值依赖最新的state和props时使用对象形式的更新会有问题。
    2. 即使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引用的是当前的实例,所以需要绑定到当前实例。

  1. constructor中绑定
  2. 定义函数使用箭头函数
  3. onClick={this.a.bind(this)}
  4. onClick={() => { this.a() }}

事件传参

  1. 通过bind方法传参事件对象可以隐式传递,事件对象 e 要排在所传递参数的后面。
a = (id, e) => {
  console.log(id, e.target.value);
}
onClick={this.a.bind(this, id)}
  1. 通过箭头函数传参事件对象必须显式传递
a = (id, e) => {
  console.log(id, e.target.value);
}
onClick={e=> {this.a(id ,e)}}
  1. 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实现方法

  1. 利用 hashchange 事件来监听 url 的 hash 变化,其兼容性较好。但是url的形式与常规url不太相同。

HashRouter的URL带有#号,可以刷新,获取URL的路由参数使用location.hash。

  1. 调用 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的路径不变

生命周期

  1. componentWillMount(不建议使用) componentDidMount 这两个生命周期函数之间会有render函数触发,组件一次挂载只会触发一次,componentDidMount多用于数据的初始化请求。可以在componentDidMount中建立定时器。对于依赖 DOM 的操作,可以把这些操作放在 componentDidMount 当中。
  2. componentWillReceiveProps(nextProps)(不建议使用) 组件render期间中的props的改变会触发此生命周期函数。
  3. shouldComponentUpdate(nextProps, nextState)(不建议使用) 返回一个布尔值,false就不会重新render,可以在你确认不需要render组件时使用。
  4. componentWillUpdate(nextProps, nextState)(不建议使用) componentDidUpdate(prevProps, prevState)这两个生命周期函数之间会有render函数触发,will时state还未改变,did时state已经改变。
  5. componentWillUnmount: 一般用于全局事件(该事件只作用于当前组件)的清除如onscroll(会先触发父组件的componentWillUnmount然后触发子组件componentWillUnmount),也会在该钩子函数中清除定时器。
  6. render函数也是生命周期函数,render渲染的为虚拟DOM,视图改变前后两次虚拟DOM会进行比较,进行diff从而更新真实DOM。
  7. 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;
}
  1. 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>
    );
  }
}
  1. 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>

新的生命周期顺序

  • 创建期
    1. constructor(构造函数只会执行一次, url上的参数放在state时要谨慎使用)
    2. static getDerivedStateFromProps
    3. render
    4. componentDidMount
  • 存在期(re-render)
    1. static getDerivedStateFromProps
    2. shouldComponentUpdate
    3. getSnapshotBeforeUpdate
    4. render
    5. componentDidUpdate
  • 销毁期
    1. 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。

  1. 组件被挂载后,回调函数会立即执行,回调函数的参数为该组件的具体实例;
  2. 组件卸载或者原来的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的条件
    1. 列表项是静态的,它们不会被计算,也不会更改。
    2. 列表中的列表项没有 ids 属性。
    3. 列表不会被重新排序或筛选。
  • 数组循环中使用的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;

组件测试

  1. 组件测试中有获取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
}