JSX
JSX是react发明的特殊写法语法,JSX=JavaScript+XML,就是JS和HTML的混合写法,这并不是JS引擎原生支持的,所以想在浏览器中直接执行,需要先转成ES5代码。jsx本质上就是一个js变量,可以作为参数、函数返回值、也可以用在循环/判断语句中。
1 2 3 4
| ReactDOM.render( <h1 id="msg"><span>hello</span><span>world</span></h1>, document.querySelector('#root') );
|
转义成为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ReactDom.render(React.createElement( "h1", { id: "msg" }, React.createElement( "span", null, "hello" ), React.createElement( "span", null, "world" ) ), document.querySelector('#root'));
|
原理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function createElement(type, props, children) { return {type, props: {...props, children}}; }
function render(virtualDOM, container) { let ele = document.createElement(virtualDOM.type); for (let attr in virtualDOM.props) { if (attr === 'children') { ele.innerHTML = virtualDOM.props[attr]; } else { ele.setAttribute(attr, virtualDOM.props[attr]); } container.appendChild(ele); } }
|
ReactDom渲染过程:
- 把jsx元素转成React.createElement方法的调用
- createElement会返回一个虚拟DOM对象
- render方法负责把虚拟DOM对象转换成真实DOM对象,并插入到容器内部
jsx规则:
- 如果需要换行的话,需要把jsx放在小括号里
- 如果想在js中显示js变量,需要放入大括号,里面可以放入js表达式
- 表达式中不能放对象,可以放字符串、数字等,还可以放函数的调用
jsx react元素的属性:
- 普通属性
- 特殊属性 class => className for => htmlFor
- 如果属性名是多个单词的话,驼峰命名法 z-index => zIndex tab-index => tabIndex
- react元素有一个非常重要的属性叫children,指这个元素的所有子元素
jsx设置事件:
给react元素设置属性 on+事件名,事件名开头大写。
ref:
ref的值是一个函数,当这个DOM挂载到页面之后会执行绑定的函数,参数就是此react元素对应的真实DOM元素。
1 2
| <input type="text" id="username" required ref={input => {console.log(input)}}/>
|
组件
组件就像一个纯函数,接受任意参数,返回一个并且只有一个react元素。声明组件有两种方式:函数组件和类组件。
渲染组件的过程:
- 初始化属性对象,然后调用类的构造函数,并把类的属性对象传进去,得到组件的实例
- 会调用实例的render方法,得到返回的react元素
- render方法会把react元素渲染成真实的DOM元素,并挂载到容器内部
函数式组件:
1. 接受一个props参数,是一个对象
2. 组件名称必须首字母大写,render只能通过首字母判断是元素还是组件(组件和元素使用方式完全相同,但渲染和使用方式不同,元素之间渲染,组件渲染返回值)
3. 组件函数要返回并且只能返回一个顶级react元素
4. 函数组件没有实例,也没有this
1 2 3 4 5 6 7
| function Welcome(props) { if (props.username) { return <h1>hello {props.username}</h1> } else { return <h1>hello stranger</h1> } }
|
类组件:
- 必须继承自React.Component
- 类组件是有实例的,需要通过this来调用属性
- 传给组件的属性会全部封装到一个对象中作为实参传给组件对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Welcome extends React.Component { render() { if (this.props.username) { return <h1>hello {this.props.username}</h1> } else { return <h1>hello stranger</h1> } } }
class App extends React.Component{ render(){ return ( <div> <Welcome username="Ryan"/> <Welcome username="Jarvan"/> <Welcome username="Wen"/> </div> ) } }
|
组件的属性和状态:
- 组件的属性是由父组件传入的,状态的值时内部初始化的
- 组件的属性不能修改, 状态的值是可以修改的
- 组件的值自己不能改,但是父组件可以改
- 组件的状态是内部初始化的,只能组件内部修改,外部不能修改
- 组件的属性和状态都是当前组件的数据源
- 组件的属性是可以自上而下流动的。单项数据流,只能父传子,只能子传父,也不能兄弟之间传递
给组件传入属性对象的一种方式:
1
| ReactDOM.render(<Comment {...comment}/>, document.querySelector('#root'));
|
组件中循环列表:
遍历列表需要给每一项设置key属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const lessons = [ {title: 'Lesson 1: title', description: 'Lesson 1: description'}, {title: 'Lesson 2: title', description: 'Lesson 2: description'}, {title: 'Lesson 3: title', description: 'Lesson 3: description'}, {title: 'Lesson 4: title', description: 'Lesson 4: description'} ];
class LessonsList extends React.Component { render() { return ( <div> {this.props.lessons.map((item, index) => { return ( <Lesson lesson={item} index={index} key={index}/> ) })} </div> ) } }
|
state
组件的状态用来描述组件内部可以变化的数据。组件的状态是类组件所特有的。当类实例化的时候,会自动调用构造函数,我们可以在构造函数中初始化状态对象。
1 2 3 4 5 6 7
| class Clock extends React.Component { constructor(props) { super(props); this.state = {time: new Date()} } }
|
修改state需要掉用实例的setState方法,setState方法用来修改状态,传入一个增量对象,会覆盖同名属性或增加新属性,不会删除老的属性。调用setState不但会修改状态,还会重新调用render方法。除了构造函数外,永远不要直接操作state。
为了提高性能,react可能会把多个setState调用合并成一个。
1 2 3
| componentDidMount() { this.setState({time: new Date()}); }
|
注意,setState方法是一个异步方法。
1 2 3 4 5 6 7 8 9 10 11
| this.setState((prevState) => ({number: prevState.number + 1})); this.setState((prevState) => ({number: prevState.number + 2})); this.setState((prevState) => ({number: prevState.number + 3}));
this.setState({number: this.state.number+1},()=>{ this.setState({number: this.state.number+2},()=>{ this.setState({number: this.state.number+3},()=>{ console.log(this.state.number); }); }); });
|
兄弟间传递数据——状态提升:
要让两个兄弟之间传递数据,需要找到他们最近的共同祖先,然后在共同祖先组件中定义一个特权函数,子孙组件通过这个共同祖先组件的特权方法,可以改变这个共同祖先的state状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Input extends React.Component { handleChange(event) { let percent = event.target.value; this.props.setPercent(percent); }
render() { return ( <div> <input type='number' onChange={this.handleChange.bind(this)} value={this.props.percent}/> </div> ) } }
class PercentageShower extends React.Component { render() { return ( <div> { (parseFloat(this.props.percent)*100).toFixed(2)+'%' } </div> ) } }
class PercentageApp extends React.Component { constructor() { super(); this.state = {percent: 0.00}; }
setPercent = (percent) => { this.setState({percent: percent}); };
render() { return ( <div> <Input percent={this.state.percent} setPercent={this.setPercent}/> <PercentageShower percent={this.state.percent}/> </div> ) } }
|
生命周期
componentWillReceiveProps: 将要接收属性
componentWillMount: 组件将要挂载
componentDidMount: 组件完成挂载
componentWillUpdate: 组件即将更新
componentDidUpdate: 组件完成更新
componentWillUnmount: 组件即将销毁
shouldComponentUpdate: 询问组件是否更新
1 2 3 4 5 6 7 8
| shouldComponentUpdate(prevProps, prevState) { console.log('询问组件是否更新'); if (prevState.number < 10) { return true; } else { return false; } }
|
Router
Router是路由的容器。
开始使用
安装react-router-dom
1
| $ npm install react-router-dom
|
引入react-router-dom并使用
1 2 3 4 5 6 7 8 9
| import {HashRouter as Router,} from 'react-router-dom';
ReactDOM.render( <Router> <div> ... </div> </Router>, document.querySelector('#root') );
|
Router组件会给他的所有子组件传递三个属性:
history 是用来操作历史
location 路径
match 匹配上有值,匹配不上为null
isExact 是否精确匹配
params 路径参数
path 来自于路由里的path属性
url 来自于url地址中的#
当有路径参数的时候path和url不一样
Route
Route是路由规则
基本使用
使用时,首先使用Link组件实现跳转(类似于Vue中的router-link),然后使用Route组件,通过传入path和component属性,匹配路径默认匹配前缀,只要当前url路径和path的前缀相同就表示能匹配上,然后渲染对应的组件(一组Route类似于Vue中的router-view)。
需要精确匹配,需要给Route组建设置exact属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { HashRouter as Router, Route, Link } from 'react-router-dom';
ReactDOM.render( <Router> <div> <ul> <li><Link to="/">首页</Link></li> <li><Link to="/user">用户</Link></li> <li><Link to="/profile">个人</Link></li> </ul> <Route exact path="/" component={Home}/> <Route path="/user" component={User}/> <Route path="/profile" component={Profile}/> </div> </Router>, document.querySelector('#root') );
|
Link组件中的to属性也可以写作一个对象
1 2 3 4 5 6
| <Link to={{ pathname: '/courses', search: '?sort=name', hash: '#the-hash', state: { fromDashboard: true } }}/>
|
Switch
如果希望最多指向一个组件,则需要把若干个路由由switch包裹起来。
1 2 3 4 5
| <Switch> <Route exact path="/" component={Home}/> <Route path="/user" component={User}/> <Route path="/login" component={Login}/> </Switch>
|
路由组件渲染三种方法
1
| <Route path="/" component={Home}/>
|
- render 是一个函数,返回值是一个组件,路径匹配则渲染
因为返回的组件不是Router组件的子组件,所以没有Router组件传递过来的三个属性,可用withRouter组件解决,也可直接给返回的组件传入三个属性
1 2 3 4
| <Route {...rest} render={({location,history,match}) => ( <Profile history={history}/> : )}/>
|
- children 是一个函数,返回一個React DOM元素,无论匹不匹配都渲染
1 2 3
| <Route path={to} children={({match}) => ( <li className={match ? "active" : ""}><Link to={to}>{children}</Link></li> )}/>
|
URL参数
在Route中,设置路径参数,在Link组件中直接访问按照需要的路径参数跳转链接即可。
1 2 3
| <Route path="/user/detail/:id" component={UserDetail}/>
<Link to={{pathname: `/user/detail/${user.id}`, state: {user}}}>{user.username}</Link>
|
认证保护路由
受保护路由是指如果当前用户未登录,则不能访问被保护的路由。
原理是利用Route组件的render渲染方式,根据条件判断渲染不同组件。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
export default ({component: Component, ...rest}) => { return ( <Route {...rest} render={({location}) => ( localStorage.getItem('login') ? <Component/> : <Redirect to={{pathname: "/login", state: {from: location.pathname}}}/> )}/> ) }
<ProtectedRoute path="/profile" component={Profile}/>
|
自定义链接
利用Route组件的children渲染方式,对组件进行操作。
1 2 3 4 5 6 7 8
|
export default ({to, children,}) => { return ( <Route exact path={to} children={({match}) => ( <li className={match ? "active" : ""}><Link to={to}>{children}</Link></li> )}/> )};
|
阻止导航
使用Prompt组件,当when属性的值为true的时候,弹出confirm框。
1 2 3 4 5
| import {Prompt} from 'react-router-dom'; <Prompt when={this.state.isEditing} message={location => `切换到${location.pathname}?`} />
|
未匹配
一般放在路由规则最后,配合Switch组件使用,用于404场景。
1
| <Route component={NoMatch}/>
|