React
脚手架create-react-app XXX
JSX写法不同:
- class ->className
- 在 JSX 中不能使用 if else 语句,但可以使用 conditional (三元运算) 表达式来替代。
- JSX 允许在模板中插入数组,数组会自动展开所有成员;
创建组件的方式
1. 构造函数
function Cmp(props){
//console.log(props)
return <p>第一种方式--{props.name}</p>
}
ReactDOM.render(
<Cmp name="zs" age={20}></Cmp>, //外部传入数据
document.getElementById('app')
)
2. class关键字
class Person extends React.Component{
//constructor(props){
//super(props)
//在此处props不能直接使用,须先定义;
//}
// 必须定义一个render函数(实例方法)
render(){
// 必须返回一个null或者虚拟DOM元素
return <div>
<h1>这是用 class 关键字创建的组件!</h1>
</div>;
}
}
两种方式的区别:
- 构造函数在使用外部传入的数据时,props不能直接使用,需要先(参数位置)定义;class在使用外部数据时,可直接通过this.props.xx的方式使用props。
有状态组件VS无状态组件
有状态组件:用class关键字创建出来的组件;无状态组件:用构造函数创建出来的
两者之间的本质区别是:有无state私有属性! 有无生命周期!
问题:什么时候使用有状态组件?什么时候使用无状态的?
- 当组件需要存放自己的私有数据,或需要在不同状态下执行不同的业务逻辑,则适合第一种;
- 若只需要根据外界传递的props渲染固定的页面,则适合第二种;(相比第一种,少了生命周期)
State与Props
props是property的缩写,可以理解为HTML标签的attribute。只要是组件的props都是只读的;
props
是用于整个组件树中传递数据和配置。
两者的区别:
- state只存在于组件内部,只能从当前组件调用
this.setState
修改state值(不可以直接修改this.state!
)。
一般更新子组件都是通过更新state值,然后更新子组件的props
值。
通常用props传递大量数据,state用于存放组件内部一些简单的定义数据(状态值)。
容器组件VS展示组件
基本原则:容器组件负责数据获取,展示组件负责根据props展示信息。
受控组件VS非受控组件
组件通信
通信是单向的,数据必须由一方传到另一方。
父向子通信
通过 props 的方式
class Parent extends Component{
state = {
msg: 'start'
};
render() {
return <Child msg={this.state.msg} />;
}
}
class Child extends Component{
render() {
return <p>{this.props.msg}</p>
}
}
子向父通信
子组件向父组件通讯,同样也需要父组件向子组件传递 props 进行通讯,但是父组件传递的是作用域为父组件自身的函数,子组件调用该函数,将子组件想要传递的信息作为参数,传递到父组件的作用域中。
class Parent extends Component{
state = {
msg: 'start'
};
transferMsg(msg) {
this.setState({
msg
});
}
render() {
return <div>
<p>子组件传递过来的信息: {this.state.msg}</p>
<Child change = {msg => this.transferMsg(msg)} />
</div>;
//使用箭头函数(使内部this仍指向父组件),将父组件的transferMsg函数通过props传递给子组件
}
}
class Child extends Component{
componentDidMount() {
setTimeout(() => {
this.props.change('end') //子组件调用该函数,将子组件想要传递的信息作为参数
}, 1000);
}
// ....
}
组件的生命周期
defaultProps
在组件创建之前,会先初始化默认的props属性,这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。在组件被创建并加载时,首先调用 constructor 构造器中的 this.state = {},来初始化组件的状态。
-
组件生命周期分为三部分:
- 组件创建阶段:特点,只执行一次;
componentWillMount: 组件将要被挂载,此时还没有开始渲染虚拟DOM
render:第一次开始渲染真正的虚拟DOM,当render执行完,内存中就有了完整的虚拟DOM了
componentDidMount: 组件完成了挂载,此时,组件已经显示到了页面上,进入运行中的状态- 组件运行阶段:特点,根据组件的state和props的改变,有选择性的触发0次或多次;
componentWillReceiveProps: 组件将要接收新属性,此时,只要这个方法被触发,就证明父组件为当前子组件传递了新的属性值;
shouldComponentUpdate: 组件是否需要被更新,此时,组件尚未被更新,但是,state 和 props 肯定是最新的
componentWillUpdate: 组件将要被更新,此时,尚未开始更新,内存中的虚拟DOM树还是旧的
render: 此时,又要重新根据最新的 state 和 props 重新渲染一棵内存中的 虚拟DOM树,当 render 调用完毕,内存中的旧DOM树,已经被新DOM树替换了!此时页面还是旧的
componentDidUpdate: 此时,页面又被重新渲染了,state 和 虚拟DOM 和 页面已经完全保持同步- 组件销毁阶段:特点,只执行一次;
componentWillUnmount: 组件将要被卸载,此时组件还可以正常使用;
总结:
- componentWillReceiveProps(nextProps)
- shouldComponentUpdate(nextProps, nextState)
- componentWillUpdate(nextProps, nextState)
- render()
- componentDidUpdate(prevProps, prevState)
路由管理Redux-router
1.装包;yarn add react-router-dom
使用教程:https://reacttraining.com/react-router/web/guides/quick-start
特点: 1.路由也是组件;(视图的一部分)2.分布式配置;3.包含式匹配;(可匹配多个)
HashRouter
表示一个路由的根容器,它包含了所有跟路由相关的内容,只需引入一次;(不跳转,不发送请求到后台)
BrowserRouter
会跳转、使用HTML5历史API
MemoryRouter>
在内存让你的“URL”的历史记录
Switch
包裹着路由,只选择其中一个
Route
路由规则,有两个属性:path、component
Link
表示路由的链接,类似Vue中的、此外在 vue 中有个 router-view 的路由标签,专门用来放置匹配到的路由组件,但在 react-router 中,并没有类似于这样的标签,而是直接把 Route 标签,当作 占位符
Redirect
重定向
基本使用:
import {HashRouter,Route,Link,Switch,Redirect} from 'react-router-dom'
<HashRouter>
<div>
<Link to="/">首页</Link>
<Link to="/news/1">新闻1</Link>
<Link to="/news/2">新闻2</Link>
//配置路由规则;添加 exact 属性,表示启用精确匹配模式
<Switch>
<Route path="/" exact component={Cmp1} />
<Route path="/news/:id" component={Cmp2} />
//404
<Route component={NoMatch}></Route>
<Redirect to="/"></Redirect>
</Switch>
</div>
function News({match,history,location}){
//match-参数获取等路由信息;history-导航;location-url定位
return <div>
{match.params.id}
<button onClick={history.goBack}>后退</button>
<button onClick={()=>history.push('/')}>回到首页</button>
</div>
}
</HashRouter>
路由守卫:定义可以验证的高阶组件
function PrivateRoute({component:Component,...rest}){
return(
<Route {...rest}
render={props=>auth.isLogin ?
(<Component {...props}/>):
(<Redirect to={pathname:"/login",
state:{from:props.location.pathname}
})
}
)
}
//模拟接口;
auth={
isLogin:false,
login(cb){
this.isLogin:true;
setTimeout(cb,300)
}
}
//登录组件;
class Login extends Component{
state={isLogin:false};
login=()=>{
auth.login(()=>{
this.setState({isLogin:true})
})
}
}
数据交互
1. fetch
用法如下:
async componentDidMount(){
let res = await fetch('/data/1.json');
let data = await res.json();
...//业务逻辑
}
2. axios
装包–>引入
async componentDidMount(){
try{
let {data} = await axios.get('/data/1.json');
...//业务逻辑
}catch(e){
alert('..')
}
}
【重点】高阶组件
高阶组件:定义一个函数,传入一个组件,返回另外一个组件,返回的另外一个组件包裹了传入的组件。
分类:属性代理高阶组件,反向继承高阶组件。
作用:代码复用,渲染节时。
// 例1:高阶函数
function hello(){
console.log('hello I love React')
}
function WrapperHello(fn){
return function(){
console.log('before say hello')
fn()
console.log('after say hello')
}
}
hello = WrapperHello(hello)
hello()
//例2:属性代理
function WrapperHello(Comp){
class WrapComp extends React.Component{
render(){
return (<div>
<p>这是HOC高阶组件特有的元素</p>
<Comp name='text' {...this.props}></Comp>
</div>)
}
}
return WrapComp
}
WrapperHello(
class Hello extends React.Component{
render(){
return <h2>hello I love React&Redux</h2>
}
}
)
//例3:反向继承
function WrapperHello(Comp){
class WrapComp extends Comp{
componentDidMount(){
console.log('高阶组件新增的生命周期,加载完成')
}
render(){
return <Comp></Comp>
}
}
return WrapComp
}
WrapperHello(
class Hello extends React.Component{
render(){
return <h2>hello I love React&Redux</h2>
}
}
)
装饰器(Decorator)
//定义一个函数,也就是定义一个Decorator,target参数就是传进来的Class。
//这里是为类添加了一个静态属性
function testable(target) {
target.isTestable = true;
}
//语法:只要Decorator后面是Class,默认把Class当成参数隐形传进Decorator,
//类似testable(MyTestableClass)
@testable
class MyTestableClass {}
console.log(MyTestableClass.isTestable) // true
//定义一个组件
class Home extends React.Component {
//....
}
//以往从状态树取出对应的数据,让后通过props传给组件使用通过react-redux自带的connect()方法
export default connect(state => ({todos: state.todos}))(Home);
//使用装饰器后如下:
@connect(state => ({ todos: state.todos }))
class Home extends React.Component {
//....
}
中间件redux-sage
处理异步请求;
import { takeEvery , put} from 'redux-saga/effects'
import axios from 'axios';
import { GET_INIT_LIST } from './actionType';
import { initListAction} from './actionCreator';
function* todolist() {
//异步获取数据
try{
const res = yield axios.get('api/list');
const action=initListAction(res.data);
yield put(action);
}catch(e){
console.log('list.json 网络请求失败')
}
}
//当type为GET_INIT_LIST的action触发时,调用todolist函数--类似是个监听器
function* mySaga() {
yield takeEvery(GET_INIT_LIST, todolist);
}
export default mySaga;
//创建store时配置redux-sage
import { createStore, applyMiddleware ,compose} from 'redux';
import reducer from './reducer'
import createSagaMiddleware from 'redux-saga' //核心函数
import mySaga from './sagas'
//1.创建中间件
const sagaMiddleware = createSagaMiddleware();
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
const enhancer = composeEnhancers(applyMiddleware(sagaMiddleware));
//2.应用中间件
const store = createStore(reducer, enhancer);
sagaMiddleware.run(mySaga)
export default store;
//组件中和之前一样;
componentDidMount(){
const action=getInitListAction();
store.dispatch(action);
}