1.组件化的优势:
1> 可组合:一个组件可以和其他组件一起使用或者可以直接嵌套在一个组件内部
2>可重用:每个组件都有独立的功能,可以被多个场景重复使用
3>可维护:每个组件仅仅包含自身逻辑,更容易被维护。
2.安装脚手架初始化项目:
npm install create-react-app –g(全局安装)
create-react-app 名 (初始化项目)
npm start
3.jsx语法注意事项:
1>jsx元素放到ReactDOM.render()函数中,最外层只有一个根节点
2>jsx通过{}使用表达式,不可使用语句
3>jsx特殊的行内属性:className,htmlFor,style要写成对象格式
4>jsx并不是真正的DOM而是虚拟DOM,最终通过bable编译成普通对象
4.列表条件渲染:
1>列表渲染:运用map方法,数据有多少项生成多少标签,
2>条件渲染:if-else语句(例如珠峰课堂的点击消失隐藏)
5.创建虚拟DOM
React.createElement(type,props,…children)
type:标签类型;props:行内属性;children:type的子元素
6.定义组件的要求:
1>组件名字首字母大写,区别原生js,
2>组件定义后,就可以当做一个标签在jsx语法中使用
3>如果使用函数定义必须返回一个jsx元素
7.class和function定义的组件有什么不同:
不管React还有Vue都是数据驱动的,都是数据映射视图,组件中数据的来源通过state和props,function定义的组件可读性高,减少了代码冗余,编写更便利,但是function是无状态的组件,不会被实例化,所以没有生命周期的钩子,不能访问this.state,只能通过props访问,class生命的组件是以Es6的形式创建的,是被实例化的组件所以可以访问this和生命周期。
8.状态(state)映射视图:
状态的组件私有的数据,
修改数据:
this.setstate({
this.setstate(prevState) => {
}
})
9.props校验:
安装第三方依赖:
yarn add prop-types –save
static.propTypes = {
name:PropType .string.isRequired//要求名字必须的字符串类型,并且必须传
age:PropType .number.isRequired
}
static defaultProps = {
name:wk
}
还可以设置props的默认值。
10.父子组件通信:
父传子:通过this.props传给子组件,
子传父:子组价不可直接修改父组件的数据,可在父组件定义修改数据的方法,把方法传给子组件。
例:
class Panel extends Component {
static defaultProps = {
a: 1
}
constructor () {
super()
this.state = {
color: 'success'
}
}
changeColor = (color) => {
this.setState({
color
})
}
render () {
return (<div className="container">
<div className={`panel panel-${this.state.color}`}>
<div className="panel-heading">
{this.props.head}
</div>
<div className="panel-body">
{this.props.body}
</div>
{/*通过 modifyColor 这个 props 把 Panel 组件的 changeColor 方法传递给 Footer 组件*/}
<Footer type={this.state.color}
modifyColor={this.changeColor} />
</div>
</div>)
}
}
class Footer extends Component {
change = () => {
this.props.modifyColor('danger')
}
render () {
return (<div className="panel-footer">
<button className={`btn btn-${this.props.type}`} onClick={this.change}>变色</button>
</div>)
}
}
11.受控组件:
在表单元素中,input,textarea,select根据用户输入改变自身状态,而React中的状态放在state中成为单一数据源,即为受控组件。(表单元素的value值绑定state属性,当value值发生变化时,更新state数据)
例:
change = (e) = {
this.setstate ({
val: e.target.value
})
}
<input value={this.state.val} onChange={this.change}></input>
12.非受控组件:
非受控组件得值存在DOM上,需要通过ref获取。
this.myRef = React.createRef()//在构造函数中创建ref
在需要获取的标签内绑定ref = {this.myRef}
在ComponentDidMount() { console.log(this.myRef.current) }
13.生命周期钩子函数:
1>constructor 构造函数
2>componentWillMount 组件将要挂载
3>render渲染函数
4>componentDidMount组件已挂载
发送ajax,操作dom
5>componentWillReceiveProps初始化组件不会执行,当父组件数据发生变化触发这个钩子
6>shouldComponentUpdate props或state发生变化时更新视图
7>componentWillUpdate 组件更新前调用
8>componentDidUpdate 组件更新后调用
9>componentsWillUnmount 组件卸载或销毁前
清除定时器,解绑事件
14.redux
1>创建store需要redux的createStore方法,导出一个含有reducer函数的createStore
export default createStore(reducer)
2>reducer有两个参数,(state,action)state是托管的数据,action是修改状态的方法
import { createStore } from 'redux'
// 使用 redux 我们不能直接修改状态,我们需要定义修改状态的函数,这个函数称为 reducer
// 一个用来管理状态的函数
function reducer(state = { num: 0 }, action) {
// state 就是当前 redux 托管的数据对象,在创建 reducer 时给 state设置的默认值 就是 state 的初始值
// action 是修改状态具体的动作以及修改状态需要的参数,是一个带type字段的对象
{ type: 'ADD', ...payload } ,而 reducer 的作用就是根据不同的 action.type 返回一个新的状态对象
switch (action.type) {
case 'ADD':
return {
num: state.num + action.amount
}
case 'MINUS':
return {
num: state.num - action.amount
}
}
// 使用 reducer 首先需要返回一个默认状态
return state
}
// 创建 store,只需要把 reducer 传递给 createStore
let store = createStore(reducer)
export default store
通过store.getState().num方法初始化私有数据
通过store.dispacth({
type:’ADD’,amount:1
})派发一个修改状态的方法
订阅状态更新,视图更新
componentDidMount () {
// 订阅状态变化后做的事情
this.unsub = store.subscribe(() => {
this.setState({
num: store.getState().num
})
})
}
componentWillUnmount () {
// 组件销毁时取消订阅
this.unsub()
}
15.reducer合并:
redux是单一的状态树,如果有多个组件的状态,就需要用combineReaducers整合,并把整合后的combineReaducers传给createStore
let combinedReducer = combineReducers({
todo: todo,
counter: counter
})
let store = createStore(combinedReducer)
export default store
整合后的reducer就会产生命名空间,初始化时需要用
num:store.getState().add.num
class Counter extends Component {
constructor (props, context) {
super()
this.state = {
num: store.getState().counter.num
}
}
react-redux:
在主入口引入Provider组件,这个组件把store引入组件树
ReactDOM.render(<Provider store={store}>
<Counter />
</Provider>, document.getElementById('root'))
16.中间件:
redux-logger:当store中的状态发生变化时,将前后的变化输出到控制台,
redux-thunk:处理一些异步操作:例如ajax,定时器等
定义一个方法 返回一个函数,函数中包含dispatch和getState方法
export default {
add (amount) {
return (dispatch, getState) => {
// 返回一个 函数,在我们可以在任意位置 dispatch action,异步的也可以
setTimeout(() => {
dispatch({
type: Types.ADD,
amount
})
}, 2000)
}
},
redux-promise:
minus (amount) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
type: Types.MINUS,
amount
})
}, 2000)
})
}
}
17.组合:
1.通过<Task></Task>(Task为组件名)标签内包含的内容通过this.props.chlidren来获取
2.把内容插入到指定的位置上;
把需要插入的数据用变量接收
let h1 = <h1>这个是个头</h1>
let body = <div><p>这是主题</p></div>
let footer = <footer><button className="btn">取消</button></footer>
把变量通过props,写成行内属性
<div className="col-md-6 col-md-offset-3">
<Panel header={h1} body={body} footer={footer}></Panel>
</div>
最后在指定位置上通过{this.props.header},{this.props.body}获取
传来的数据。
constructor的第二个参数Context
若有父,子,孙三个组件,在传递数据是先让父组件传给子组件,子组件在传给孙组件十分麻烦。
Context提供了组件间共享值的方法,
先创建一个Context默认共享数据,
const ThemeContext = React.createContext(‘111’)
<ThemeContext.Provider value=”red”>
<Son></Son>
</ThemeContext.Provider>
通过value绑定共享的值,在组件中使用时声明一个静态属性static contextType = themContext,其他组件或者render中就可以通过this.context.属性名获取共享的值
在函数式定义组件时用到Consumer组件
function ThemeButton(props) {
return (<ThemeContext.Consumer>
{
value => <button className={`btn btn-${value.theme}`}>{value.theme}</button>
}
</ThemeContext.Consumer>)
}
18.路由:
<HashRouter>
<Route path=’/home’ component={Home}> </Route>
<HashRouter>//HashRouter只能包含一个根标签
Route标签有两个属性,path地址栏中的地址,component是对应的组件,当地址栏中的地址与path一致时会渲染对应的组件
<Link to=”/home”>首页</Link>
this.props.history.push(‘/user’)//手动代码切换路由
动态路由:
this.state.list.map((item, index) => {
return <li key={index} className='list-group-item'>
<Link to={`/user/detail/${item.id}`}>id: {item.id}; Username: {item.name}</Link>
</li>
})
}
路由匹配都是模糊匹配,
例:
<div>
+ <Route path='/' render={() => <h1>首页</h1>}></Route>
+ <Route path='/:name' render={() => <h1>MABIN</h1>}></Route>
<Route path="/home" component={Home} />
<Route path="/profile" component={Profile} />
<Route path="/user" component={User} />
</div>
当我们访问/home时,首页和马宾依然会被渲染,
<Route exact path='/' render={() => <h1>首页</h1>}></Route>
exact 属性会精准匹配,只有访问‘/’,才会显示首页
Switch组件匹配一个后就不在匹配了(需要导入内部组件)
路由保护,权限验证(重点)
例如点击登录后,在localStorage中存一个变量记录是否登录过,
localStorage.setItem(‘islogin’,ture);
this.props.history.push(this.props.location.from)//代码跳转路由到要去的却被拦下的路由
被设置权限的组件中:
export.default ({component:Component,…others} ) => {
return <Route {…others} render={(props) => {
return localStorage.getItem(‘islogin’)
? <Component {…props}/>
: <Redirect to={{pathname:’/login’,from:props.match.url}}></Redirect>
Redirect 重定向到登录页
}
}></Route>
}
把被保护的路由从原组件替换成权限验证组件(用到了条件渲染)
<PrivateRoute path="/profile" component={Profile} />
<PrivateRoute path="/user" component={User} />
Prompt组件在用户要从某个页面离开的时候提示用户:
有一个when属性,为true弹框提醒,为false不提醒
massage属性,值是一个函数,接收一个要去往的路由信息,返回提示信息
<Prompt when={this.state.show} message={location => `您确定要去 ${location.pathname}?`}></Prompt>