豪翔天下

Change My World by Program

0%

React 开发手册

基本概念

  • React是一个用于构建用户界面的Javascript库,是DOM的一个抽象层
  • React主要用于构建UI

状态管理

State

Props

  • props.children,类似于vue种的槽,只要组件内部有元素,那么它就是props.children,内部可以直接拿来用,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <MyComponent>
    <div>test</div>
    </MyComponent>

    function MyComponent(props) {
    return (
    <div>
    {props.children}
    </div>
    )
    }

Effect Hook

  • 副作用:数据获取、设置订阅、手动更改DOM
  • 可以把useEffect Hook看作componentDidMount、componentDidUpdate、componentWillUnmount这三个函数的组合
  • 默认useEffect在第一次渲染之后和每次更新之后都会执行
  • 在函数组件中执行副作用操作,可以直接使用state,不用编写class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React, { useState, useEffect } from 'react';

function Example() {
const [count, setCount] = useState(0);

// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;

// 在useEffect中可以这样使用异步方法,如果直接useEffect(async()=>{})的话会报错:useEffect function must return a cleanup function or nothing, Promises ... are not supported, but you can call an async function inside an effect.
async function fetchAPI() {}
fetchAPI();
});

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

Context API

  • Redux类似,也有一个Provider在最外面
  • 还是觉得Redux方便好理解一点,而且功能强大一点,Redux才是做了一个完整的状态管理功能,context主要就是存储一个全局的数据,当然使用了context数据的的UI组件在context数据变化时也会刷新,是整个UI树的更新,用户体验和性能都会有问题
1
2
3
4
5
const WebContext: Context<boolean> = createContext<boolean>(true);

<WebContext.Provider value={isWeb}>
<ReduxProvider store={store}></ReduxProvider>
</WebContext.Provider>

路由

  • 主要使用的是React Router,包括react-routerreact-router-domreact-router-native

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { Switch, Route } from 'react-router-dom'

    const Main = () => (
    <main>
    <Switch>
    <Route path='/roster' component={Home}/>
    <Route path='/schedule' component={Post}/>
    <Route path='/about', render={() => <About something={this.something}>}/> <!--通过这种方式绑定数据或者方法给子路由-->
    </Switch>
    </main>
    )

    this.props.location.pathname; // 获取当前的url路径
  • 需要注意的是,如果是Link链接的路由和当前路由是一样的,那么页面不会发生跳转,什么都不会做,这时候如果是弹出菜单,弹出菜单也不会自动关闭,所以这种情况可以单独处理一下,用a标签代替一下,然后使用window.location.href来进行跳转吧,例如:

    1
    2
    3
    4
    5
    6
    function jumpToMenu(url, e) {
    e.preventDefault()
    window.location.href = url
    }

    <a href={url} onClick={(e) => jumpToMenu(url, e)}>{text}</a> // 在onClick里面传递参数

组件

组件定义

函数组件

  • 函数组件中能使用useState
1
2
3
4
5
6
7
8
9
10
// typescript中需要这样定义,FC是TypeScript使用的一个范性,意思是FunctionComponent
// typescript中需要定义入参类型,可以这样定义
interface CurrentProps {
field1: string;
field2: number;
}

const MyComponent: FC<CurrentProps> = (props) => {

}

组件属性

  • 动态添加元素样式style

    1
    <div style={{display: this.state.show ? "block" : "none"}}>动态样式</div>
  • 动态添加类class

    1
    2
    <div className={this.state.show ? "show-class" : "hide-class"}>动态类</div>
    <div className={style.style1 + ' ' + style.style2}>多个类</div>

常用组件

Prompt

  • 在路由即将切换前弹出确认框(离开前确认)
  • 需要注意的是,如果用户点击了取消,那么会组织路由的切换,但是用户的点击事件如果有监听,依然会触发点击事件的
  • 最好在message里面判断是否需要展示Prompt,不要在when里面,因为在when里面,每次重新render都会执行,但是message里面只是在用户真的打算跳转的时候才会执行
1
2
3
<Prompt 
when={true}
message={(params) => params.pathname == '/当前路径' ? true : "确认离开" } /> // 当返回文字的时候会弹出确认,而返回true的时候则不会弹出

父子组件资源共享

父组件将方法作为props传递给子组件

1
2
3
4
5
6
// 父组件
[MyState, SetMyState] = useEffect(false);
<Child setMyState={SetMyState}>

// 子组件
props.setMyState(true);

JSX语法

基本语法

1
2
3
4
5
6
7
8
<div tabIndex="0">ttt</div>	// 可以使用双引号来直接指定属性值
<img src={user.avatarUrl}</img> // 也可以使用大括号来指定变量属性值

// 传递变量到scss中
const myStyle = `--bg-url: ${myUrl}`
<div style={myStyle}></div> // 可以通过这种方式将变量

{/ 注释 /}

代码片段fragments

  • 为一个组件添加元素,并且不会在DOM中增加额外的节点,常规的做法是在外层包一个div,这样就会多一层DOM元素,为了减少元素数量,可以这样做
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);

// 简写语法<>
return (
<>
<td>mycontent</td>
<td>mycontent</td>
</>
)

条件渲染

如果是在JSX外部的js部分代码,那么直接使用js自己的if或者其他条件判断即可完成。在JSX内部的话一般则是使用逻辑与&&或者三目运算符完成。例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
render() {
return (
<div>
{this.state.posts.length > 0 && // 注意两边是大括号
<p>There is some posts</p>
}
<p>
{length > 0 && '还可以这样直接写文字'}
</p>
</div>
<div>
<p>There is {this.state.posts !== undefined ? this.state.posts.length : 0} posts.</p>
</div>
)
}

map循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Nav>
{
props.menus?.map((menu, i) => {
return (
menu?.children?.length > 0
? <NavDropdown title={menu.title} key={i}>
{
menu.children?.map((subMenu, subIndex) => {
<NavDropdown.Item href={subMenu.url} key={`${subIndex}-${i}`}>{subMenu.title}</NavDropdown.Item>
})
}
</NavDropdown>
: <NavLink href={menu.url} title={menu.title} key={i}/>
)
})
}
</Nav>

必备三方组件

prop-types

  • 类似于typescript,能够有效限制与定义组件中的prop参数类型
  • 不过建议在pages页面(非component)中禁用eslint的react/prop-types: 'off'功能,因为页面的props可能太多,也和component中的有重复
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
import PropTypes from 'prop-types';

MyComponent.propTypes = {
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool.isRequired, // 布尔值,isRequired表示必填
optionalFunc: PropTypes.func,
optionalObject: PropTypes.object,
optionalSymbol: PropTypes.symbol,
requiredAny: PropTypes.any.isRequired, // 任意类型
optionalEnum: PropTypes.oneOf(['News', 'Photos']), // 枚举类型
optionalUnion: PropTypes.oneOfType([ // 可以是多个类型
PropTypes.string, // 字符串类型
PropTypes.number, // 数字类型
PropTypes.instanceOf(Message)
]),
optionalArrayOf: PropTypes.arrayOf(PropTypes.number), // 指定数组元素
optionalObjectWithShape: PropTypes.shape({ // 定义对象的内部字段
color: PropTypes.string,
fontSize: PropTypes.number
}),
optionalObjectWithStrictShape: PropTypes.exact({ // 只允许特定的值
name: PropTypes.string,
quantity: PropTypes.number
}),
}

React-Redux

  • 从后端的角度看,就是一个维护全局变量的东西(也有同一个页面不同组件使用相同的东西,比如当前用户的用户名用户头像啥的)
  • Action定义了要发生什么,并且携带着数据(可以在action里面调用API,将结果进行dispatch,类似于vuex中的action将结果进行mutation),reducer用来定义发生该事情后需要做什么(类似于vuex中的mutation),selector可以理解是从state获取数据的API。
  • Redux可以通过connect方法,将storedispatch方法保存到组件的props
  • stateprops的对应通常需要使用mapStateToProps这个函数进行定义。它默认会订阅Store,每当state更新的时候,就会自动执行,重新计算UI组件的参数
  • 下面的方法在根组件外面包了一层Provider,这样所有的子组件默认都能拿到store
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
50
51
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import rerducer from './reducers'
import App from './App'

const store = createStore(
reducer
);

store.subscribe(subscribe) // 设置监听函数,一旦State发生变化,就自动执行这个函数

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root")
);

// actions,和vuex中的actions类似
export function getUser(token: string) {
return async (dispatch) => {
const { data } = await fetcher.post(...)
// dispatch的调用方法和vuex中的mutation类似
dispatch({
type: 'SET_USER',
payload: data.user,
});
};
}

// reducer类似于vuex中的mutation
const reducer: Reducer<AppState, AppActionTypes> = (
state = initialState,
action
) => {
switch (action.type) {
case 'SET_USER':
if (typeof localStorage !== "undefined") {
localStorage.setItem('USER_KEY', JSON.stringify(action.payload));
}
return {
...state,
user: action.payload,
};
default:
return state;
}
};

const store = useSelector<State, AppState>((state) => state.app);
const isAuth = !!store.token && !!store.user && !!store.user.name;
  • 可以用connect将组件与store进行连接,它可以接收四个参数:
    • mapStateToProps(state, ownProps) : stateProps,第一个参数就是Redus的store,这个方法的返回值就会作为组件的props

Redux-Saga

  • 从后端的角度看,就是在启动应用的时候,再启动一些单独的线程,这些线程可以异步去做些更改变量或者监听的事情,类似于钩子。

  • 可以在这里对异步操作进行集中处理。

  • Effect的几种方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import {take,call,put,select,fork,takeEvery,takeLatest} from 'redux-saga/effects'
    // take: 用来监听action,返回监听到的action对象,例如如果有一个type=login的action,那么在执行dispatch(loginAction)之后,就可以这样获取对象
    const action = yield take('login')

    // call: 调用指定函数
    yield call(myfunc, param1, param2);

    // put: 触发dispatch,用于发送action,dispatch in saga
    yield put({type: 'login'})

    // select: 和getState类似,用于获取store中的state
    const state = yield select()

    // fork

    // takeEvery/takeLatest: 用于监听
  • 简单的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 在编写saga的文件里面一般这样写钩子函数
    export function * mySaga(action) { // 这里的参数action,是包含了payload,state的对象
    console.log("Hello Saga");
    }

    // 在main.js中,这样引入saga中间件
    import createSagaMiddleware from 'redux-saga';
    import {helloSaga} from './saga.js';
    const sageMiddleware = createSagaMiddleware();
    const store = createStore(rerducer, aplyMiddleware(sagaMiddleware));
    sagaMiddleware.run(helloSaga);
  • saga中使用fetch

    1
    2
    3
    function* onGetSuccess(state) {
    const res = yield fetch("https://example.com").then(response => response.json());
    }

Redux-actions

redux-actionsreact-saga配合使用可以简化大量的重复代码。在之前我们要创建一个action需要线这样子定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 在使用redux-actions之前,需要这样创建一个action并使用
export const saveResult = (resultList) => {
console.log(resultList);
return {
type: "SAVE_RESULT",
resultList
}
};

// 现在只需要这样定义多个动作
export const actionCreators = createActions({
["SAVE_RESULT"]: resultList=>resultList,
});

Styled-Components

  • 目的是将React组件包装成Styled组件
1
2
3
4
5
6
7
8
9
10
11
12
13
import { Card } from 'antd';

const StyledComponent = styled(Card)` // 可以接收一个React-Componetn例如Card,也可以接收一个tagName例如div
div {
color: red;
}
.abc {
color: blue;
width: ${props => props.width} // 变量传递
}
`
// 在render里面就可以直接使用该组件了
<StyledComponent width={"12px"}></StyledComponent>

SWR

  • state-while-revalidate的缩写,是HTTP RFC 5861中描述的一种Cache-Control扩展
  • ReactHook组件,用于缓存从远端获取的数据
  • 常用语多次请求相同URL获取数据,或者更新数据后,先返回缓存的响应,与此同时去后台发送新的请求,以提高响应速度,减少等待时间
  • 请求的结果根据标识key放在redux里缓存,取数据的时候直接从redux里面取
  • SWR并不是一个请求库,只是一种管理数据的方式
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
import useSWR from 'swr';

// 请求参数
// key: 表示请求的标识,可以是任意字符串,但是我们一般会设置为请求的url,这样方便识别。如果key传入null就代表不请求数据,什么都不做
// fetcher: 返回请求数据的异步方法,一般会是一个axios对象
// options: 其它配置项
// 返回值
// data: 相应数据
// error: 错误
// isValidating: 是否正在请求或重新验证数据
// mutate(data?, shouldRevalidate): 用于直接修改缓存数据
const { data, error, isValidating, mutate } = useSWR(key, fetcher, options);


const fetcher = Axios.create({
baseURL: 'https://haofly.net/api/',
responseType: "json",
});

const { data: user } = useSWR('/user', fetcher); // SWR会将key作为参数传递给fetcher
const { data: users, mutate } = useSWR(['/users', userIds], fetcher); // 可以传递多个参数,swr会将多个参数组合为一个key

import useSWR, { mutate } from 'swr';
mutate('/users'); // 手动再次获取数据,会让所有拥有相同key的swr主动去获取一次数据,并更新缓存

mutate(); // 或者直接用useSWR返回的mutate,可以省略key

mutate('/users', {...users, new: true}); // 触发更新操作,但是在更新操作完成前先直接用第二个参数来代替缓存,相当于先直接修改缓存
mutate({...users, new: true}); // 使用返回的mutate,省略key

const { newDate } = await axios.patch('/users'); // 更新users操作,直接返回新的数据
mutate(updated, false); // 如果更新操作直接返回更新后的资源,那么mutate可以直接使用它,然而最后一个参数设置为false,这样就可以不用去请求获取新的资源了,表示无需重新验证资源

事件

支持事件列表

1
2
3
// 焦点事件
onBlur
onFocus

TroubleShooting

  • React 表达式必须有一个父元素: 常出现在JSX的渲染的内嵌语句中返回了错误格式的结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    render() {
    return (
    {
    [ // 需要返回的应该是数组而不是混搭
    <p>abc</p>,
    <p>def</p>,
    ]
    })
    }

扩展阅读

坚持原创技术分享,谢谢支持

欢迎关注我的其它发布渠道