JSX是什么?JSX是JavaScript的语法扩展,用来描述用户界面使用规范JSX语句本身也是表达式,在JS代码中可以作为普通表达式使用JSX语句中引号包裹字符串,大括号包裹JS表达式JSX语言特性沿袭JS特性,因此属性名使用驼峰式规范JSX默认进行防注入攻击,渲染前会过滤所有传入值,并且将所有内容都转为字符串扩展模块由于 JSX 编译后会调用 React.createElement 方法,所以在包含JSX的模块中必须先引入React 模块标签名内置组件标签名以小写字母开头,如<div><span>。自定义组件标签名以大写字母开头标签名可以使用点表示法 <MyComponents.DatePicker color=“blue” />标签名不能为表达式function Story(props) { // 错误!JSX 标签名不能为一个表达式。 return <components[props.storyType] story={props.story} />;}function Story(props) { // 正确!JSX 标签名可以为大写开头的变量。 const SpecificStory = components[props.storyType]; return <SpecificStory story={props.story} />;}属性如果你没有给属性传值,它默认为 true如果已经有一个props对象,可以使用扩展运算符传递整个属性对象子代子代可以是字符串常量,JSX,JS表达式,函数布尔值、Null 和 Undefined 被忽略本质Babel转译器会将JSX转译为React.createElement方法,该方法首先会进行一些避免bug的检查,之后返回React元素(一个JavaScript对象)。JSX 只是为 React.createElement(component, props, …children) 方法提供的语法糖。React元素渲染渲染过程React DOM 使 React 元素 和 浏览器 DOM 的数据内容保持一致通过ReactDOM.render() 将React元素渲染到页面上更新元素React元素是一个不可变的(immutable),元素被创建之后无法改变其内容和属性若要更新,创建一个新的React元素,重新调用ReactDOM.render()渲染 React DOM 首先会比较元素内容先后的不同,而在渲染过程中只会更新改变了的部分组件是什么?独立的、可复用的部件接收任意的输入值(称之为“props”),并返回一个React元素。定义方法函数定义function Welcome(props) { return <h1>Hello, {props.name}</h1>;}类定义class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; }}Propsfunction Welcome(props) { return <h1>Hello, {props.name}</h1>;}ReactDOM.render( <Welcome name=“Sara” />, document.getElementById(‘root’));调用自定义组件,组件上的属性集合到props对象上传递给组件所有的React组件必须像纯函数(输入值不会被改变,输入值相同总会输出相同的结果)那样使用它们的props。PropTypes类型检查PropTypes 包含一整套验证器,可用于确保你接收的数据是有效的。当你给属性传递了无效值时,JavsScript 控制台将会打印警告。出于性能原因,propTypes 只在开发模式下进行检查。注意: React.PropTypes 自 React v15.5 起已弃用。请使用 prop-types 库代替。defaultProps 属性默认值类型检查发生在 defaultProps 赋值之后,所以类型检查也会应用在 defaultProps 上面。State & 生命周期State状态,组件内部维护构造函数是唯一能够初始化this.state的地方this.props和this.state的更新可能是异步的,React 可以将多个setState() 调用合并成一个调用来提高性能。// Wrongthis.setState({ counter: this.state.counter + this.props.increment,});// Correctthis.setState((prevState, props) => ({ counter: prevState.counter + props.increment}));setState()是一个请求// 不依赖state计算时书写// callback在setState执行完成同时组件被重渲之后执行,等同于componentDidUpdate的作用this.setState({ counter: 2}, [callback]);// 依赖state之前的状态this.setState((prevState) => { return {counter: prevState.quantity + 1};});单向数据流,通过state维护组件内部状态,通过props向子组件传递数据生命周期defaultProps, propTypesconstructor() - 构造函数是初始化状态的合适位置。UNSAFE_componentWillMount() - 装配发生前调用,在这方法里同步地设置状态将不会触发重渲render() - 渲染时调用componentDidMount() - 组件装配完成时调用,初始化DOM节点,发送网络请求,事件订阅UNSAFE_componentWillReceiveProps(nextProps) - 装配了的组件接收到新属性前调用,即使属性未有任何改变,也可能会调用该方法,请确保比较前后值shouldComponentUpdate(nextProps, nextState) - 当接收到新属性或状态时在渲染前调用,该方法并不会在初始化渲染或当使用forceUpdate()时被调用UNSAFE_componentWillUpdate() - 接收到新属性或状态时时调用,不能在这调用this.setState()getDerivedStateFromProps(nextProps, prevState) - 组件实例化后和接受新属性时调用,调用this.setState() 通常不会触发,返回响应属性getSnapshotBeforeUpdate(prevProps, prevState) - 在最新的渲染输出提交给DOM前将会立即调用,这一生命周期返回的任何值将会 作为参数被传递给componentDidUpdate()componentDidUpdate(prevProps, prevState) - 更新发生后立即调用,不会在初始化渲染时调用。操作DOM,发送请求componentWillUnmount() - 组件被卸载和销毁之前立刻调用。清理定时器、请求、DOM节点、事件订阅事件处理this类的方法默认不会绑定this,解决办法:在构造函数中使用bind绑定使用实验性的属性初始化器class LoggingButton extends React.Component { // This syntax ensures this
is bound within handleClick. // Warning: this is experimental syntax. handleClick = () => { console.log(’this is:’, this); } render() { return ( <button onClick={this.handleClick}> Click me </button> ); }}使用箭头函数定义回调函数class LoggingButton extends React.Component { handleClick() { console.log(’this is:’, this); } render() { // This syntax ensures this
is bound within handleClick return ( <button onClick={(e) => this.handleClick(e)}> Click me </button> ); }}每次渲染时会创建一个不同的回调函数,如果回调函数作为一个属性值传入低阶组件,会导致额外渲染eventReact封装的一个合成事件SyntheticEvent事件处理函数返回false不会再阻止事件传播, 所以必须得手动触发e.stopPropagation()和e.preventDefault() 方法。boolean bubblesboolean cancelableDOMEventTarget currentTargetboolean defaultPreventednumber eventPhaseboolean isTrustedDOMEvent nativeEventvoid preventDefault()boolean isDefaultPrevented()void stopPropagation()boolean isPropagationStopped()DOMEventTarget targetnumber timeStampstring type传递参数class Popper extends React.Component{ constructor(){ super(); this.state = {name:‘Hello world!’}; } preventPop(name, e){ //事件对象e要放在最后 e.preventDefault(); alert(name); } render(){ return ( <div> <p>hello</p> {/* Pass params via bind() method. /} <a onClick={this.preventPop.bind(this, this.state.name)}>Click</a> <a onClick={(e) => {this.preventPop(this.state.name, e)}}>Click</a> </div> ); }}条件渲染在render函数中使用条件语句实现条件渲染,可以使用变量来储存元素在JSX中使用条件运算符组成表达式实现条件渲染让 render 方法返回 null隐藏组件列表通过使用{}在JSX内构建一个元素集合key每个列表元素需要有key属性keys可以在DOM中的某些元素被增加或删除的时候帮助React识别哪些元素发生了变化key会作为给React的提示,但不会传递给你的组件function NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> ); return ( <ul>{listItems}</ul> );}表单受控组件通过onChange+setState控制状态改变,使React控制组件状态<input type=“text” value={this.state.value} onChange={this.handleChange} /><textarea value={this.state.value} onChange={this.handleChange} /><select value={this.state.value} onChange={this.handleChange}> <option value=“grapefruit”>Grapefruit</option> <option value=“lime”>Lime</option> <option value=“coconut”>Coconut</option> <option value=“mango”>Mango</option></select>当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择做什么。非受控组件通过ref获取获取DOM在 React 的生命周期中,表单元素上的 value 属性将会覆盖 DOM 中的值使用defaultValue指定初始值状态提升使用 react 经常会遇到几个组件需要共用状态数据的情况。这种情况下,我们最好将这部分共享的状态提升至他们最近的父组件当中进行管理。组合 vs 继承通过子代props.children或者自定义属性承载组件使用组合实现包含关系,动态定义传入容器的子组件使用组合实现特殊实例,替换继承的作用,比如说PromReact理念组件按单一功能封装设定最小可变状态集,要点是 DRY:不要重复(Don’t Repeat Yourself)。找出应用程序的绝对最小表示并计算你所需要的其他任何请求。例如,如果你正在创建一个 TODO 列表,只要保存一个包含 TODO 事项的数组;不要为计数保留一个单独的状态变量。相反,当你想要渲染 TODO 计数时,只需要使用 TODO 数组的长度就可以了。Refs & DOMReact v16.3创建ref React.createRef()关联ref,可以使用直接关联也可使用回调class CustomTextInput extends React.Component { constructor(props) { super(props); // 创建 ref 存储 textInput DOM 元素 this.textInput = React.createRef(); this.buttonInput = React.createRef(); this.focusTextInput = this.focusTextInput.bind(this); this.setTextInputRef = element => { this.buttonInput = element; }; } focusTextInput() { // 直接使用原生 API 使 text 输入框获得焦点 // 注意:通过 “current” 取得 DOM 节点 this.textInput.current.focus(); } render() { // 告诉 React 我们想把 <input> ref 关联到构造器里创建的 textInput
上 return ( <div> <input type=“text” ref={this.textInput} /> <input type=“button” value=“Focus the text input” onClick={this.focusTextInput} ref={this.setTextInputRef} /> </div> ); }}ref 的更新会发生在componentDidMount 或 componentDidUpdate 生命周期钩子之前。性能优化使用生产版本开发模式下React会更大更慢,因此一定要用生产版本部署使用 Chrome Performance 归档组件在项目地址栏内添加查询字符串 ?react_perf(例如, http://localhost:3000/?react_perf)。打开Chrome开发工具Performance 标签页点击Record.执行你想要分析的动作。不要记录超过20s,不然Chrome可能会挂起。停止记录。React事件将会被归类在 User Timing标签下。避免重复渲染使用SCU控制避免数据突变支持ES6的情况下,使用Object.assign/扩展运算符返回新数据,防止引用类型数据突变使用Immutable.js保持数据不可变,降低出错率Reconciliation两个不同类型的元素将产生不同的树。通过渲染器附带key属性,开发者可以示意哪些子元素可能是稳定的。Context共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。const {Provider, Consumer} = React.createContext(defaultValue);<Provider value={/ some value /}><Consumer> {value => / render something based on the context value /}</Consumer>每当Provider的值发送改变时, 作为Provider后代的所有Consumers都会重新渲染。 从Provider到其后代的Consumers传播不受shouldComponentUpdate方法的约束,因此即使祖先组件退出更新时,后代Consumer也会被更新。Fragments聚合一个子元素列表,代替组件根元素使用<></> 是 <React.Fragment/> 的语法糖。<></> 语法不能接受键值或属性,要添加key属性需要使用<React.Fragment/>PortalsPortals 提供了一种将子节点渲染到父组件以外的 DOM 节点的方式。ReactDOM.createPortal(child, container)// child 可渲染的React子元素// container DOM元素虽然DOM树中的位置平行,但React树中组件仍在根组件树中,树组件仍能捕获到事件错误边界是什么?错误边界组件用于捕获其子组件树 JavaScript 异常,记录错误并展示一个回退的 UI 。错误边界在渲染期间,生命周期方法内,以及整个组件树构造函数内捕获错误。错误边界无法捕获事件处理函数内部的错误,事件处理函数内部的错误使用try…catch捕获。错误边界无法捕获异步事件的错误,异步事件的错误在回调函数内部自行处理。错误边界无法捕获服务器端渲染和错误边界自身抛出的错误。自 React 16 开始,任何未被错误边界捕获的错误将会卸载整个 React 组件树。组件内部如果一个类组件定义了 componentDidCatch(error, info): 方法,则该组件是一个错误边界组件。class ErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } componentDidCatch(error, info) { // error: 错误信息 // info: 组件栈追踪 this.setState({ hasError: true }); logErrorToMyService(error, info); } render() { if (this.state.hasError) { // You can render any custom fallback UI return <h1>Something went wrong.</h1>; } return this.props.children; }}组件使用错误边界的粒度完全取决于你的应用。你可以将其包装在最顶层的路由组件并为用户展示一个 “发生异常(Something went wrong)“的错误信息,就像服务端框架通常处理崩溃一样。你也可以将单独的插件包装在错误边界内部以保护应用不受该组件崩溃的影响。Web ComponentsWeb Components是W3C规范的新功能,提供自定义元素封装功能。相当于在DOM的层面封装一个自定义元素,元素的展示和特性在Web Components内部实现。在React眼中,一个Web Component和一个普通元素无异。React组件是React层面的元素单元,封装有UI、逻辑、数据响应。高阶组件(HOC)是什么?对组件逻辑进行重用的一种抽象模式高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件const EnhancedComponent = higherOrderComponent(WrappedComponent);通过将原组件 包裹 在容器组件里面的方式来 组合 使用原组件。高阶组件就是一个没有副作用的纯函数。需避免的问题请使用组合性高阶组件,避免使用更改性高阶组件在高阶组件内部修改(或以其它方式修改)原组件的原型属性,称为更改性高阶组件function logProps(InputComponent) { InputComponent.prototype.componentWillReceiveProps(nextProps) { console.log(‘Current props: ‘, this.props); console.log(‘Next props: ‘, nextProps); } // 我们返回的原始组件实际上已经 // 被修改了。 return InputComponent;}// EnhancedComponent会记录下所有的props属性const EnhancedComponent = logProps(InputComponent);在高阶组件内部使用组合返回一个新的组件,称为组合型高阶组件function logProps(WrappedComponent) { return class extends React.Component { componentWillReceiveProps(nextProps) { console.log(‘Current props: ‘, this.props); console.log(‘Next props: ‘, nextProps); } render() { // 用容器组件组合包裹组件且不修改包裹组件,这才是正确的打开方式。 return <WrappedComponent {…this.props} />; } }}高阶组件应该传递与它要实现的功能点无关的props属性render() { // 过滤掉与高阶函数功能相关的props属性, // 不再传递 const { extraProp, …passThroughProps } = this.props; // 向包裹组件注入props属性,一般都是高阶组件的state状态 // 或实例方法 const injectedProp = someStateOrInstanceMethod; // 向包裹组件传递props属性 return ( <WrappedComponent injectedProp={injectedProp} {…passThroughProps} /> );}最大化使用组合const ConnectedComment = connect(commentSelector, commentActions)(Comment);connect函数返回一个高阶组件使用compose代替高阶组件嵌套使用const enhance = compose( // 这些都是单参数的高阶组件 withRouter, connect(commentSelector))const EnhancedComponent = enhance(WrappedComponent)不要在render()中调用高阶函数将静态方法做拷贝,当使用高阶组件包装组件,原始组件被容器组件包裹,也就意味着新组件会丢失原始组件的所有静态方法。使用hoistNonReactStatic处理高阶组件返回组件命名function withSubscription(WrappedComponent) { class WithSubscription extends React.Component {/ … /} WithSubscription.displayName = WithSubscription(${getDisplayName(WrappedComponent)})
; return WithSubscription;}function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || ‘Component’;}refs不会被传递到被包裹组件,React16.3版本中加入React.forwardRef解决这个问题function logProps(Component) { class LogProps extends React.Component { componentDidUpdate(prevProps) { console.log(‘old props:’, prevProps); console.log(’new props:’, this.props); } render() { const {forwardedRef, …rest} = this.props; // Assign the custom prop “forwardedRef” as a ref return <Component ref={forwardedRef} {…rest} />; } } // Note the second param “ref” provided by React.forwardRef. // We can pass it along to LogProps as a regular prop, e.g. “forwardedRef” // And it can then be attached to the Component. function forwardRef(props, ref) { return <LogProps {…props} forwardedRef={ref} />; } // These next lines are not necessary, // But they do give the component a better display name in DevTools, // e.g. “ForwardRef(logProps(MyComponent))” const name = Component.displayName || Component.name; forwardRef.displayName = logProps(${name})
; return React.forwardRef(forwardRef);}Render Props<DataProvider render={data => ( <h1>Hello {data.target}</h1>)}/>是什么?一个函数属性,用于渲染属性名不一定是render,重要的是它起到的作用注意在使用时最好指定propTypeprops检查优化,直接使用函数在render时会返回新函数;可以将函数定义为实例方法使用,从而避免这一问题与第三方库协同与jQuery协同,把DOM操作交给jQuery,React数据驱动渲染与jQuery DOM分离可访问性JSX支持所有的aria- HTML属性JSX中for特性被写作htmlFor代码分割import()动态导入 + react-loadable模块import { BrowserRouter as Router, Route, Switch } from ‘react-router-dom’;import Loadable from ‘react-loadable’;const Loading = () => <div>Loading…</div>;const Home = Loadable({ loader: () => import(’./routes/Home’), loading: Loading,});const About = Loadable({ loader: () => import(’./routes/About’), loading: Loading,});const App = () => ( <Router> <Switch> <Route exact path="/" component={Home}/> <Route path="/about" component={About}/> </Switch> </Router>);APIcreateElement()React.createElement( type, [props], […children])cloneElement()React.cloneElement( element, [props], […children])以 element 作为起点,克隆并返回一个新的React元素(React Element)。生成的元素将会拥有原始元素props与新props的浅合并。新的子级会替换现有的子级。来自原始元素的 key 和 ref将会保留。isValidElement()验证对象是否是一个React元素。返回 true 或 false 。React.ChildrenReact.Children 提供了处理 this.props.children 这个数据结构的工具。React.Children.map(children, function[(thisArg)])React.Children.forEach(children, function[(thisArg)])React.Children.count(children)React.Children.only(children)React.Children.toArray(children)ReactDOMimport ReactDOM from ‘react-dom’ReactDOM.render( element, container, [callback])ReactDOM.findDOMNode(component)