react
基本全是组件,须要使用什么组件就安装什么组件。css
npm install antd -D
// 全局导入使用 import React, {Component} from 'react' import Button from 'antd/lib/button' import 'antd/dist/antd.css' export default class AntdTest extends Component { render(){ return ( <div> <Button type="primary">按钮</Button> </div> ) } }
全局导入代码会比较大,不利于项目开发,建议使用按需加载的方式。vue
npm install react-app-rewired@2.0.2-next.0 babel-plugin-import -D
const { injectBabelPlugin } = require('react-app-rewired'); module.exports = function override(config, env) { config = injectBabelPlugin( // 在默认配置基础上注入 // 插件名,插件配置 ['import', { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }], config ); return config }
"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" }
import React, {Component} from 'react' import { Button } from 'antd' export default class AntdTest extends Component { render(){ return ( <div> <Button type="primary">按钮</Button> </div> ) } }
基本原则:容器组件负责数据获取,展现组件负责根据
props
展现信息
// CommentList.js import React,{Component} from 'react'; export default class CommentList extends Component { constructor(props){ super(props); this.state = { comments: [] }; } componentDidMount(){ setTimeout(() => { this.setState({ comments: [ { body: 'react is very good', author: 'facebook' }, { body: 'vue is very good', author: 'youyuxi' } ] }) },1000) } render(){ return ( <div> {this.state.comments.map((c,i) => <Comment key={i} data={c}></Comment>)} </div> ) } } // 展现组件 (傻瓜组件) class Comment extends Component { console.log('render comment') return ( <div> <p>{this.props.data.body}</p> <p>---{this.props.data.author}</p> </div> ) }
componentDidMount(){ setInterval(() => { this.setState({ comments: [ { body: 'react is very good', author: 'facebook' }, { body: 'vue is very good', author: 'youyuxi' } ] }) },1000) } class Comment extends PureComponent { render(){ // 由于数据的一直更新,其实数据是没有变化的,render函数却会一直执行,消耗性能 console.log('render comment') return ( <div> <p>{this.props.data.body}</p> <p>---{this.props.data.author}</p> </div> ) } }
PureComponent
(15.3出现)定制了shouldComponentUpdate
后的Component
(浅比较)
由于是浅比较,只会比较引用类型地址,只会比较一层。因此要么传值类型,要么引用类型地址不能发生变化而且地址只能有一层。大体源码解析以下:react
export default function PureComponent(props, context){ Component.call(this,props,context) } PureComponent.prototype = Object.create(Component.prototype) PureComponent.prototype.constructor = PureComponent PureComponent.prototype.isPureReactComponent = true // 着重扩展了shouldComponentUpdate的实现 PureComponent.prototype.shouldComponentUpdate = shallowCompare // 浅比较 function shallowCompare(nextProps, nextState){ return !shallowEqual(this.props,nextProps) || !shallowEqual(this.state, nextState) } export default function shallowEqual(objA, objB){ // 比较的是对象的引用,只会看引用地址是否发生变化 if(objA === objB){ return true } if(typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null){ return false } var keysA = Object.keys(objA) var keysB = Object.keys(objB) if(keysA.length !== keysB.length){ return false } // test for A's keys different from B. // 只作了一层循环 for(var i = 0; i < keysA.length; i++){ if(!objB.hasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]){ return false } } return true }
import React,{Component, PureComponent} from 'react'; export default class CommentList extends Component { render(){ return ( <div> {this.state.comments.map((c,i) => ( // <Comment key={i} data={c}></Comment> // <Comment key={i} body={c.data.body} author={c.data.author}></Comment> <Comment key={i} {...c}></Comment> ))} </div> ) } } class Comment extends PureComponent { // class Comment extends Component{ render(){ console.log('render comment') return ( <div> {/* <p>{this.props.data.body}</p> <p>---{this.props.data.author}</p> */} <p>{this.props.body}</p> <p>---{this.props.author}</p> </div> ) } } // react 16.6.0版本以上扩展出一个高阶组件 React.memo 实现了PureComponent // 可用该组件改写上述代码 const Comment = React.memo(props => { return ( <div> <p>{props.body}</p> <p>---{props.author}</p> </div> ) })
提升复用率,首先想到的就是抽离相同逻辑,在React里就有了HOC(higher-Order Component)
的概念。
高阶组件简单讲就是一个函数,接收一个组件返回一个新组件,产生的新组件能够对属性进行包装,也能够重写部分生命周期。
// Hoc.js import React, {Component} from 'react' function Study(props){ // stage 是从父组件传入的,name是动态获取的 return <div>{props.stage}-{props.name}</div> } // 高阶组件 const WithStudy = Comp => { // 获取name,name可能来自接口或其余手段 const name = '高阶组件'; return props => <Comp {...props} name={name}></Comp> } const NewStudy = WithStudy(Study); export default class Hoc extends Component { render(){ return ( <div> <NewStudy stage="React"></NewStudy> </div> ) } }
// 高阶组件 const WithStudy = Comp => { // 获取name,name可能来自接口或其余手段 const name = '高阶组件'; return class NewComp extends Component { componentDidMount(){ console.log('do something') } render(){ return <Comp {...this.props} name={name}></Comp> } } // return props => <Comp {...props} name={name}></Comp> }
// Hoc.js function Study(props){ return <div>{props.stage}-{props.name}</div> } const WithStudy = Comp => { // 获取name,name可能来自接口或其余手段 const name = '高阶组件'; // 可省略类名 return class NewComp extends Component { componentDidMount(){ console.log('do something') } render(){ return <Comp {...this.props} name={name}></Comp> } } } const widthLog = Comp => { console.log(Comp.name + '渲染了') return props => <Comp {...props}></Comp> } // 链式调用 const NewStudy = WithStudy(widthLog(Study)); export default class Hoc extends Component { render(){ return ( <div> <NewStudy stage="React"></NewStudy> </div> ) } }
由于链式语法太过累赘,es7有一个优秀的语法-装饰器,专门处理这种问题,只能用于class
组件webpack
npm install babel-plugin-transform-decorators-legacy -D
// config-overrides.js 增长 config = injectBabelPlugin( ['@babel/plugin-proposal-decorators', { 'legacy': true }], config ) 修改了配置文件记得重启服务
import React, {Component} from 'react' import { render } from 'react-dom'; // 高阶组件 const WithStudy = Comp => { // 获取name,name可能来自接口或其余手段 const name = '高阶组件a'; // 可省略类名 return class NewComp extends Component { componentDidMount(){ console.log('do something') } render(){ return <Comp {...this.props} name={name}></Comp> } } } const widthLog = Comp => { console.log(Comp.name + '渲染了') return props => <Comp {...props}></Comp> } // 调用执行顺序从上往下,装饰的是下面紧挨着的class @widthLog // 装饰的是Study @WithStudy // 装饰的是上一层包装返回的新组件 @widthLog // 装饰的是前两层包装返回的新组件 // 这里的study就是进行修饰过的最新的study,这就是为何高阶组件代码要放在上面 class Study extends Component { render(){ return <div>{this.props.stage}-{this.props.name}</div> } } export default class Hoc extends Component { render(){ return ( <div> <Study stage="React"></Study> </div> ) } }
Composition
组件复合给与你足够的敏捷去定义自定义组件的外观和行为,并且是以一种明确而安全的方式进行。若是组件有公用的非ui
逻辑,将它们抽取为js
模块导入使用而不是继承它。
// Composition.js import React from 'react'; // Dialog做为容器,不关心内容和逻辑 // 等于vue中的slot function Dialog(props){ // children 预留属性固定的 return <div style={{border: "4px solid blue"}}>{props.children}</div> } // WelcomeDialog经过复合提供内容 function WelcomeDialog(){ return ( <Dialog> <h1>欢迎光临</h1> <p>感谢使用react</p> </Dialog> ) } export default function(){ return <WelcomeDialog></WelcomeDialog> }
import React from 'react'; // Dialog做为容器,不关心内容和逻辑 // 等于vue中的slot function Dialog(props){ // children 预留属性固定的 return <div style={{border: `4px solid ${props.color || 'blue'}`}}>{props.children}</div> } // WelcomeDialog经过复合提供内容 function WelcomeDialog(props){ return ( <Dialog {...props}> <h1>欢迎光临</h1> <p>感谢使用react</p> </Dialog> ) } export default function(){ return <WelcomeDialog color="green"></WelcomeDialog> }
vue
,具名插槽怎么处理?import React from 'react'; // Dialog做为容器,不关心内容和逻辑 // 等于vue中的slot function Dialog(props){ // children 预留属性固定的 return ( <div style={{border: `4px solid ${props.color || 'blue'}`}}> {/* 可理解为匿名插槽 */} {props.children} <div className="footer"> {/* footer是jsx */} {props.footer} </div> </div> ) } // WelcomeDialog经过复合提供内容 function WelcomeDialog(props){ return ( <Dialog {...props}> <h1>欢迎光临</h1> <p>感谢使用react</p> </Dialog> ) } export default function(){ const footer = <button onClick={() => alert('肯定!')}>肯定</button> return <WelcomeDialog color="green" footer={footer}></WelcomeDialog> }
children
的深层次解读children多是jsx,也多是函数,也多是数组
一、对children做为函数解读web
import React from 'react'; const Api = { getUser(){ return { name: 'pj', age: 20 } } } function Fetcher(props) { const user = Api[props.name](); return props.children(user) } export default function(){ return ( <div> <Fetcher name="getUser"> {({name,age}) => ( <p> {name} - {age} </p> )} </Fetcher> </div> ) }
二、利用children是数组作滤器npm
import React from 'react'; function Filter(props){ return ( <div> {/* React.Children 提供了不少方法,而且会有异常捕获判断 */} {React.Children.map(props.children,child => { if(child.type !== props.type){ return } return child })} </div> ) } export default function(){ return ( <div> {/* 过滤器能够过滤指定标签类型 */} <Filter type="p"> <h1>react</h1> <p>react很不错</p> <h1>vue</h1> <p>vue很不错</p> </Filter> </div> ) }
三、修改childrenjson
import React from 'react'; // 修改children function RadioGroup(props){ return ( <div> {React.Children.map(props.children, child => { // vdom不可更改,克隆一个新的去修改 return React.cloneElement(child, {name: props.name}) })} </div> ) } function Radio({children, ...rest}){ return ( <label> <input type="radio" {...rest} /> {children} </label> ) } export default function(){ return ( <div> <RadioGroup name="mvvm"> <Radio value="vue">vue</Radio> <Radio value="react">react</Radio> <Radio value="angular">angular</Radio> </RadioGroup> </div> ) }
Hook
是React16.8
一个新增项,它可让你在不编写class
的状况下使用state
以及其余的React
特性。api
Hook
的特色// HookTest.js import React, {useState} from 'react' export default function HookTest(){ // useState(initState) 返回一个数组,索引0的位置存放的是定义的状态,索引1的位置是改对应状态的函数 const [count, setCount] = useState(0); return ( <div> <p>点击了{count}次</p> <button onClick={() => setCount(count+1)}>点击</button> </div> ) }
import React, {useState} from 'react' export default function HookTest(){ // 多个状态 const [age] = useState(20); const [fruit, setFruit] = useState('banana'); const [input, setInput] = useState(''); const [fruits, setFruits] = useState(['apple','banana']); return ( <div> <p>年龄:{age}</p> <p>选择的水果:{fruit}</p> <p> <input type="text" value={input} onChange={e => setInput(e.target.value)} /> <button onClick={() => setFruits([...fruits,input])}>新增水果</button> </p> <ul> {fruits.map(f => <li key={f} onClick={() => setFruit(f)}>{f}</li>)} </ul> </div> ) }
Effect Hook
useEffect
就是一个Effect Hook
,给函数组件增长了操做反作用的能力。它跟class
组件中的componentDidMount
,componentDidUpdate
和componentcomponentWillMount
具备相同的做用,只不过被合并成了一个API
.数组
import React, {useState, useEffect} from 'react' export default function HookTest(){ const [count, setCount] = useState(0); // 反作用钩子会在每次渲染时都执行 useEffect(() => { document.title = `您点击了${count}次` }) return ( <div> <p>点击了{count}次</p> <button onClick={() => setCount(count+1)}>点击</button> </div> ) }
// 若是仅打算执行一次,传递第二个参数为[],相似于componentDidMount // useEffect能够有多个,便于逻辑的拆分 useEffect(() => { // api 调用 console.log('api 调用') },[])
// 只有count发生变化,该方法才会执行 useEffect(() => { document.title = `您点击了${count}次` },[count]) // 若是跟多个值有关 useEffect(() => { document.title = `您点击了${count}次` },[count,age])
Custom Hook
自定义hook
是一个函数,名称用'use
'开头,函数内部能够调用其余钩子
import React, {useState, useEffect} from 'react' function useAge(){ const [age, setAge] = useState(0); useEffect(() => { setTimeout(() => { setAge(20) },2000) }) return age } export default function HookTest(){ const age = useAge(); return ( <div> <p>年龄:{age ? age : 'loading...'}</p> </div> ) }
Hook
useContext
、useReducer
、useCallback
、useMemo
安全
Context
上下文提供一种不须要每层设置
props
就能跨多级组件传递数据的方式
Context
相关API
React.createContext
Context.Provider
Class.contextType
Context.Consumer
一、第一种 消费者Consumer方式
import React from 'react' // 建立上下文 const MyContext = React.createContext(); const {Provider, Consumer} = MyContext; function Child(prop){ return ( <div> Child: {prop.foo} </div> ) } export default function ContextTest(){ return ( <div> {/* 套在Provider下,无论嵌套多少层,均可以拿到Provider上传过来的值 */} <Provider value={{foo: 'bar'}}> {/* 第一种 消费者Consumer方式 想拿到值的组件必须被Consumer包围 */} <Consumer> {value => <Child {...value}></Child>} </Consumer> </Provider> </div> ) }
二、消费方法二、Hook
import React, {useContext} from 'react' // 建立上下文 const MyContext = React.createContext(); const {Provider} = MyContext; // 使用Hook消费 function Child2(){ const context = useContext(MyContext); return <div>Child2: {context.foo}</div> } export default function ContextTest(){ return ( <div> {/* 套在Provider下,无论嵌套多少层,均可以拿到Provider上传过来的值 */} <Provider value={{foo: 'bar'}}> {/* 消费方法二、Hook */} <Child2></Child2> </Provider> </div> ) }
三、消费方法三、contextType
import React, {Component} from 'react' // 建立上下文 const MyContext = React.createContext(); const {Provider} = MyContext; // 使用class指定静态contextType class Child3 extends Component{ static contextType = MyContext; render(){ return <div>Child3: {this.context.foo}</div> } } export default function ContextTest(){ return ( <div> {/* 套在Provider下,无论嵌套多少层,均可以拿到Provider上传过来的值 */} <Provider value={{foo: 'bar'}}> {/* 消费方法三、contextType */} <Child3></Child3> </Provider> </div> ) }
// KForm.js import React, { Component } from 'react' import {Input, Button} from 'antd' // 建立一个高阶组件,扩展示有表单,事件处理,数据收集,校验 function KFormCreate(Comp){ return class extends Component { constructor(props){ super(props); this.options = {}; this.state = {}; } handleChange = e => { const {name, value} = e.target; this.setState({ [name]: value }, ()=>{ // 确保值发生变化再校验 this.validateField(name); }) } // 单项校验 validateField = field => { // 一、获取校验规则 const rules = this.options[field].rules; // 任意一项失败则返回false const ret = !rules.some(rule => { if(rule.required){ if(!this.state[field]){ // 校验失败 this.setState({ [field + 'Message']: rule.message }) return true } } }) // 校验成功 if(ret){ this.setState({ [field + 'Message']: '' }) } return ret } // 校验全部字段 validate = cb => { const rets = Object.keys(this.options).map(field => this.validateField(field) ); const ret = rets.every( v => v == true); cb(ret,this.state) } // 建立input包装器 getFieldDec = (field,option) => { // 保存当前输入项配置 this.options[field] = option; return InputComp => ( <div> {React.cloneElement(InputComp, { name: field, value: this.state[field] || '', onChange: this.handleChange })} {/* 校验错误信息 */} {this.state[field + 'Message'] && ( <p style={{color: 'red'}}>{this.state[field + 'Message']}</p> )} </div> ) } render(){ return <Comp getFieldDec={this.getFieldDec} validate={this.validate}></Comp> } } } @KFormCreate class KForm extends Component { onSubmit = () => { // 校验全部项 this.props.validate((isValid, data) => { if(isValid){ // 提交登陆 console.log('登陆',data) // 后续登陆逻辑 }else { alert('校验失败') } }); } render(){ const {getFieldDec} = this.props; return ( <div> {getFieldDec('uname',{ rules: [ { required: true, message: '用户名必填' } ] })(<Input></Input>)} {getFieldDec('pwd',{ rules: [ { required: true, message: '密码必填' } ] })(<Input type="password"></Input>)} <Button onClick={this.onSubmit}>登陆</Button> </div> ) } } export default KForm