Redux学习手册—详述基本概念及基本使用等原理(附详细案例代码解析过程)

1. 重点提炼

  • redux
    • 管理应用数据(它提供了一套管理的模式)
    • 纯函数(便于测试和重构)
  • 规范
    • store:仓库
    • state:数据
    • reducer:数据操作
    • action:动作
      • dispatch:派发

2. Redux

注意ReduxReact是一点关系都没有的,它其实是js当中的一个状态(数据)管理的库。

2.1 知识点

  • 状态管理器
  • state 对象
  • reducer 纯函数
  • store 对象
  • action 对象
  • combineReducers 方法
  • react-redux
  • provider 组件
  • connect 方法
  • Redux DevTools extension 工具
  • 中间件 - middleware
  • redux-chunk

2.2 状态(数据)管理

前端应用越来越复杂,要管理和维护的数据也越来越多,为了有效的管理和维护应用中的各种数据,我们必须有一种强大而有效的数据管理机制,也称为状态管理机制,Redux 就是解决该问题的。状态管理即有一定的规则,Redux的基本功能是做数据管理的,但是仅仅把数据存起来再取,就没啥意义了,重点在于数据管理,得有一定地规则和规范,才能称之为管理

2.3 Redux

Redux 是一个独立的 JavaScript 状态管理库,与非 React 内容之一

https://www.bootcdn.cn/redux/

官网:https://www.redux.org.cn/

2.3.1 核心概念

理解 Redux 核心几个概念与它们之间的关系

  • state:存储数据的位置,对象
  • reducer:数据分片(对数据分片、构建基础数据的函数)
  • store:仓库,提供数据存储、管理等操作的对象,state、reducer、action 都是 store 其中一个功能
  • action:提交数据操作请求(这里是不能直接修改state数据的,必须先发送请求才行,如果希望数据被有效管理,则不是任何人可以去修改数据的。就如同React中props传递进来,在组件内部是千万不能修改的,而是由控制数据的这个人去修改,即假如数据存在state中,想要对数据进行操作,必须提前发送请求。发了请求之后,由reducer来决定这个数据,它是构建基础数据的函数,它来维护数据的一些基础的函数。实际上state是由reducer产生的数据,它产生后的数据就会保存在state中,如果想修改数据,则通过action操作。)
  • state、reducer、action均放在store对象中
2.3.1.1 state 对象

通常我们会把应用中的数据存储到一个对象树(Object Tree) 中进行统一管理,我们把这个对象树称为:state (与React中state是一个东西,同样React中的state也得通过setState去修改,它就类似redux中reducer了)

2.3.1.1.1 state 是只读的

这里需要注意的是,为了保证数据状态的可维护和测试,不推荐直接修改 state 中的原数据

2.3.1.1.2 通过纯函数修改 state

什么是纯函数?实际就是reducer

2.3.1.1.2.1 纯函数

特点

  1. 相同的输入永远返回相同的输出
  2. 不修改函数的输入值
  3. 不依赖外部环境状态
  4. 无任何副作用(不做与自己功能无关的事)

使用纯函数的好处

  1. 便于测试
  2. 有利重构
2.3.1.1.3 example01

纯函数可理解为目的很单纯的函数,它不会对传入的参数进行修改,举个例子:

如这就不符合纯函数了,因为其对参数进行了修改。

这样其实不利于测试,其实这样修改参数,实际就代表user1的原数据就没了,比如过来很多天,你想看过去这个变量修改前的值,就没法看了。

redux-demo\1.redux-纯函数.html

    let user1 = {
        id: 1,
        username: 'lisi',
        age: 35
    };
 
    function growUp(user) {
        user.age++;
    }
 
    growUp(user1);

所以我们不能修改参数,把函数改为:返回新对象

相同的输入有相同的输出(伪代码:合并对象)

    let user1 = {
        id: 1,
        username: 'lisi',
        age: 35
    };
 
    function growUp(user) {
        //user.age++;
 
        return {
                ...user,
                {age: user.age+1}
        }
 
    }
 
    growUp(user1);

或者:Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    let user1 = {
        id: 1,
        username: 'lisi',
        age: 35
    };
 
    function growUp(user) {
        // user.age++;
 
        // return {
        //         ...user,
        //         {age: user.age+1}
        // }
 
        return Object.assign({}, user, {age: user.age + 1});
    }
 
    let user2 = growUp(user1);
    let user3 = growUp(user2);
 
    console.log(user1);
    console.log(user2);
    console.log(user3);

image-20200608162258161

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.01
Branch:branch1

commit description:a0.01-example01(redux-纯函数)

tag:a0.01

2.3.1.2 Reducer 函数
function todo(state, action) {
  switch(action.type) {
    case 'ADD':
      return [...state, action.payload];
      break;
    case 'REMOVE':
      return state.filter(v=>v!==action.payload);
      break;
    default:
      return state;
    }
}

上面的 todo 函数就是 Reducer 函数

  1. 第一个参数是原 state 对象
  2. Reducer 函数不能修改原 state,而应该返回一个新的 state
  3. 第二参数是一个 action 对象,包含要执行的操作和数据
  4. 如果没有操作匹配,则返回原 state 对象
2.3.1.3 action 对象

我们对 state 的修改是通过 reducer 纯函数来进行的,同时通过传入的 action 来执行具体的操作,action 是一个对象

  • type 属性 : 表示要进行操作的动作类型,增删改查……
  • payload属性 : 操作 state 的同时传入的数据

但是这里需要注意的是,我们不直接去调用 Reducer 函数,而是通过 Store 对象提供的 dispatch 方法来调用

2.3.1.4 Store 对象

为了对 stateReduceraction 进行统一管理和维护,我们需要创建一个 Store 对象

2.3.1.4.1 Redux.createStore 方法
let store = Redux.createStore((state, action) => {
  // ...
}, []);

todo

用户操作数据的 reducer 函数

[]

初始化的 state

我们也可以使用 es6 的函数参数默认值来对 state 进行初始化

let store = Redux.createStore( (state = [], action) => {
  // ...
} )
2.3.1.4.2 getState() 方法

通过 getState 方法,可以获取 Store 中的 state

store.getState();
2.3.1.4.3 example02

Store 对象——创建仓库、获取state

2.3.1.4.3.1 example02-1

创建仓库方法:构建一个用来存储数据,提供数据操作的仓库

Redux.createStore : 创建仓库的函数

  • 第一个参数:reducer 函数,修改数据的方法
  • 第二个参数:仓库中存储的初始化数据,存什么?

redux-demo\2.redux基础.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script src="./redux.min.js"></script>
<script>
 
 
<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];
 
    let store = Redux.createStore( function () {
 
    }, users)
 
    console.log(store);
</script>
</body>
</html>

操作数据的方法:

image-20200608170851308

获取仓库数据:

第一个参数是函数,它的作用是初始化,定义数据如何变化的。

    let store = Redux.createStore( function () {
 
    }, users)
 
    console.log(store);
    console.log( store.getState() );

返回的是undefined

image-20200608171013063

第一个参数reducer 函数,修改数据的方法,如何操作,操作的结果?访问数据的时候,返回什么,数据变化如何进行?

创建仓库的第一个参数,即该函数其实有两个参数,该函数的返回值就是仓库中state的最新的数据。(刚才我们没有返回所有是undefined

该方法会在仓库创建的时候默认调用一次。

第一个参数函数同时它会把第二个参数作为参数,传递给第一个参数函数,这个参数函数执行完毕的返回值就是该仓库最新的数据。

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.02-1
Branch:branch1

commit description:a0.02-1-example02-1(redux基础——获取仓库数据,但state为undefined)

tag:a0.02-1

2.3.1.4.3.2 example02-2

构建一个用来存储数据,提供数据操作的仓库

Redux.createStore : 创建仓库的函数

  • 第一个参数:reducer 函数,修改数据的方法,如何操作,操作的结果?访问数据的时候,返回什么,数据变化如何进行?该函数的返回值就是仓库中state的最新的数据,该方法会在仓库创建的时候默认调用一次。
  • 第二个参数:仓库中存储的初始化数据,存什么?
<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];

    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);
        return state;
    }

    let store = Redux.createStore( reducer, users );

    console.log(store);
    console.log( store.getState() );

{type: "@@redux/INIT4.s.h.2.t.b"}是创建仓库(Redux.createStore)首次调用传进来的,是默认传的

createStore => reducer(users, {type: “@@redux/INITg.w.0.g.1.a”}) => return state => 现在仓库里面的数据。

image-20200608172119794

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.02-2
Branch:branch1

commit description:a0.02-2-example02-2(redux基础——获取仓库数据及原理)

tag:a0.02-2

2.3.1.4.4 dispatch() 方法

通过 dispatch 方法,可以提交更改

store.dispatch({
  type: 'ADD',
  payload: 'zs'
})
2.3.1.4.5 action 创建函数

action 是一个对象,用来在 dispatch 的时候传递动作和数据,我们在实际业务中可能会中许多不同的地方进行同样的操作,这个时候,我们可以创建一个函数用来生成(返回)action

function add(payload) {
  return {
    type: 'ADD',
    payload
  }
}
 
store.dispatch(add('zs'));
store.dispatch(add('ls'));
...
2.3.1.4.6 subscribe() 方法

可以通过 subscribe 方法注册监听器(类似事件),每次 dispatch action 的时候都会执行监听函数,该方法返回一个函数,通过该函数可以取消当前监听器。刚刚代码中,我们每次调用dispatch方法,就console一下,很麻烦,可以写进监听器中进行优化。

let unsubscribe = store.subscribe(function() {
  console.log(store.getState());
});
unsubscribe();
2.3.1.4.7 example03

dispatch方法:当我们调用 dispatch 的时候其实就是执行的 reducer函数

createStore=> store.dispatch({type: “@@redux/INITg.w.0.g.1.a”}}) 默认情况下

2.3.1.4.7.1 example03-1
<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];
 
 
    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);
        return state;
    }
 
    let store = Redux.createStore( reducer, users );
 
    console.log(store);
    console.log( store.getState() );
 
    store.dispatch({
        type: 'ADD', // 名字随便取
        payload: {   // 需要修改的数据(增量数据)
            id: 3,
            username: 'wangwu',
            age: 30
        }
    });

dispatch:当我们调用dispatch的时候其实就是执行的 reducer 函数

createStore =>(等同于) store.dispatch({type: “@@redux/INITg.w.0.g.1.a”}})

image-20200608173917373

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-1
Branch:branch1

commit description:a0.03-1-example03-1(redux基础——dispatch)

tag:a0.03-1

2.3.1.4.7.2 example03-2

reducer函数通过action参数来分辨,现在想做什么事情?我们根据action的值来做一些判断,做相应的操作。经常根据action.type做一些对应的操作。

<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];

    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                // 没做任何操作,就返回仓库上一次的状态
                return state;
        }
    }

    let store = Redux.createStore( reducer, users );

    console.log(store);
    console.log( store.getState() );

    store.dispatch({
        type: 'ADD', // 名字随便取
        payload: {   // 需要修改的数据(增量数据)
            id: 3,
            username: 'wangwu',
            age: 30
        }
    });

    console.log( store.getState() );

image-20200608174750526

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-2
Branch:branch1

commit description:a0.03-2-example03-2(redux基础——reduce函数使用action参数)

tag:a0.03-2

2.3.1.4.7.3 example03-3

千万不要如下返回,因为我们要保持仓库的数据与上次的数据完全隔离的。(保持纯函数特性)

<!--利用redux来管理和维护数据-->
    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];

    function reducer(state, action) {
        // state : 上一次的 数据    action:动作
        console.log('执行了', action);

        switch (action.type) {
            case 'ADD':
                // 千万不要如下返回,因为我们要保持仓库的数据与上次的数据完全隔离的
                state.push(action.payload);
                return state;
            // return [...state, action.payload];
            default:
                // 没做任何操作,就返回仓库上一次的状态
                return state;
        }
    }

    let store = Redux.createStore( reducer, users );

    console.log(store);
    console.log( store.getState() );

    store.dispatch({
        type: 'ADD', // 名字随便取
        payload: {   // 需要修改的数据(增量数据)
            id: 3,
            username: 'wangwu',
            age: 30
        }
    });

    console.log( store.getState() );

导致两次数据一样了,就追踪不到上次的数据了。

image-20200608175032032

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-3
Branch:branch1

commit description:a0.03-3-example03-3(redux基础——reduce函数使用action参数注意的问题)

tag:a0.03-3

2.3.1.4.7.4 example03-4

但每次都这样调用store.dispatch函数,会很繁琐。

\redux-demo\3.action函数.html

我们可以把这个过程封装成函数(其实就是action的工厂函数),这个函数返回一个action。这样方便我们后续代码的复用,否则每次追加数据,都需填写“type”、“payload”,很麻烦,代码也冗余。如果后续修改的添加(添加用户),如将“type”改为“ADD_USER”等,你得到处去改“type”,其实这样代码的耦合度非常高,独立性很差,维护起来很困难。其实这样也非常符合纯函数的特性。

    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];


    function reducer(state, action) {

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }

    }

    let store = Redux.createStore( reducer, users );

    console.log(store);

    console.log( store.getState() );


    function add(payload) {
        return {
            type: 'ADD',
            payload
        }
    }


    store.dispatch(add({
        id: 3,
        username: 'xm',
        age: 30
    }));

    console.log( store.getState() );

    store.dispatch(add({
        id: 3,
        username: 'xh',
        age: 30
    }));

    console.log( store.getState() );

image-20200608180047977

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-4
Branch:branch1

commit description:a0.03-4-example03-4(action工厂函数封装)

tag:a0.03-4

2.3.1.4.7.5 example03-5

以上我们每次调用store.dispatch后再console,这样很麻烦,我们可以给store添加一个监听器。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<script src="./redux.min.js"></script>
<script>



    let users = [
        {
            id: 1,
            username: 'zs',
            age: 35
        },
        {
            id: 2,
            username: 'ls',
            age: 30
        }
    ];


    function reducer(state, action) {

        switch (action.type) {
            case 'ADD':
                return [...state, action.payload];
            default:
                return state;
        }

    }

    let store = Redux.createStore( reducer, users );

    let unsubscribe = store.subscribe(function() {
        console.log(store.getState());
    });

    // console.log(store);

    // console.log( store.getState() );


    function add(payload) {
        return {
            type: 'ADD',
            payload
        }
    }


    store.dispatch(add({
        id: 3,
        username: 'xm',
        age: 30
    }));

    // console.log( store.getState() );

    store.dispatch(add({
        id: 3,
        username: 'xh',
        age: 30
    }));

    // console.log( store.getState() );


</script>
</body>
</html>

image-20200707135507181

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.03-5
Branch:branch1

commit description:a0.03-5-example03-5(加监听器)

tag:a0.03-5

2.3.2 Redux 工作流

UI界面(组件,如Vue、React等)需要数据的时候,就可以从仓库的State中拿数据了,然后展示在UI界面上,当界面上更改数据的时候,不能直接修改State,这里是单向数据流。当需要修改State时,先提交Actions动作,然后匹配Reducers对应的行为(函数)之后再执行,Reducers对应函数的返回值将作为新的State。其实React组件的设计的也是这种单向数据流,如子组件想用父组件数据,从props取数据,但是子组件想修改父组件的数据,它需要调用父组件传递的回调函数才行,也是单向数据流。其实目的就是让数据的修改可控,可追踪到数据的变化。

img

2.3.3 Reducers 分拆与融合

当一个应用比较复杂的时候,状态数据也会比较多,如果所有状态都是通过一个 Reducer 来进行修改的话,那么这个 Reducer 就会变得特别复杂。这个时候,我们就会对这个 Reducer 进行必要的拆分

let datas = {
  user: {},
  items: []
  cart: []
}

我们把上面的 usersitemscart 进行分拆

// user reducer
function user(state = {}, action) {
  // ...
}
// items reducer
function items(state = [], action) {
  // ...
}
// cart reducer
function cart(state = [], action) {
  // ...
}
2.3.3.1 combineReducers 方法

该方法的作用是可以把多个 reducer 函数合并成一个 reducer

let reducers = Redux.combineReducers({
  user,
  items,
  cart
});
 
let store = createStore(reducers);
2.3.3.2 example04

Reducers分拆与融合

2.3.3.2.1 example04-1

如下:这些数据会随着应用的复杂,数据越来越庞大,假设我们的数据非常庞大,封装reducer

    let appData = {
        // 用户数据
        users: [
 
        ],
        // 商品数据
        items: [
 
        ],
        // 购物车数据
        cart: [
 
        ]
    };
 
 
    function reducer(state, action) {
 
        switch (action.type) {
            case 'ADD_USER':
                return {
                    items: state.items,
                    cart: state.cart,
                    users: [...state.users, action.payload]
                };
            default:
                return state;
        }
 
    }
 
    let store = Redux.createStore( reducer, appData );
 
    console.log( store.getState() );
 
    function addUserAction(payload) {
        return {
            type: 'ADD_USER',
            payload
        }
    }
 
    store.dispatch( addUserAction({id: 1, username: 'lisi'}) );
    console.log( store.getState() );

image-20200608225745942

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.04-1
Branch:branch1

commit description:a0.04-1-example04-1(复杂数据封装reducer)

tag:a0.04-1

2.3.3.2.2 example04-2

但是如果user里还嵌套其他的内容,这样的话层级会非常得多,reducer返回值设置起来就会非常恶心。我们可以reducer拆开,每个reducer负责自己的模式。

 
    let appData = {
        // 用户数据
        users: [
 
        ],
        // 商品数据
        items: [
 
        ],
        // 购物车数据
        cart: [
 
        ]
    };
 
 
    // state默认是users空数组
    function usersReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_USER':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function itemsReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_ITEM':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function cartReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_CART':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    // 拆分后再融合
 
    function reducer(state, action) {
        return {
            users: usersReducer(state.users, action),
            items: itemsReducer(state.items, action),
            cart: cartReducer(state.cart, action)
        }
    }
 
    let store = Redux.createStore( reducer, appData );
 
    console.log( store.getState() );
 
    function addUserAction(payload) {
        return {
            type: 'ADD_USER',
            payload
        }
    }
 
    store.dispatch( addUserAction({id: 1, username: 'lisi'}) );
    console.log( store.getState() );

image-20200608225745942

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.04-2
Branch:branch1

commit description:a0.04-2-example04-2(复杂数据封装reducer——分拆)

tag:a0.04-2

2.3.3.2.3 example04-3

鉴于这种需求比较强烈,redux提供了一个方法:

combineReducers => 就是完成了我们上面封装的 myReducer的功能

    let appData = {
        // 用户数据
        users: [
 
        ],
        // 商品数据
        items: [
 
        ],
        // 购物车数据
        cart: [
 
        ]
    };
 

    // state默认是users空数组
    function usersReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_USER':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function itemsReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_ITEM':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    function cartReducer(state = [], action) {
        switch (action.type) {
            case 'ADD_CART':
                return [...state, action.payload];
            default:
                return state;
        }
    }
 
    // 拆分后再融合
 
    // function myReducer(state, action) {
    //     return {
    //         users: usersReducer(state.users, action),
    //         items: itemsReducer(state.items, action),
    //         cart: cartReducer(state.cart, action)
    //     }
    // }
  
    let reducer = Redux.combineReducers({
        users: usersReducer,
        items: itemsReducer,
        cart: cartReducer
    });
 
    let store = Redux.createStore( reducer, appData );
 
    console.log( store.getState() );
 
    function addUserAction(payload) {
        return {
            type: 'ADD_USER',
            payload
        }
    }
 
    store.dispatch( addUserAction({id: 1, username: 'lisi'}) );
    console.log( store.getState() );

image-20200608225745942

参考:https://github.com/6xiaoDi/blog-Redux-Novice/tree/a0.04-3
Branch:branch1

commit description:a0.04-3-example04-3(复杂数据封装reducer—Redux.combineReducers)

tag:a0.04-3

2.3.4 小结

以上我们学的redux是原生js写的,比较单一,真正在react中直接用redux,还需要做很多工作。不能直接用,如果直接用会出现很多问题。如数据变化以后,组件更新的问题以及调用等其他问题。

因此react还有一个第三方库,叫react-redux,创建仓库都类似,重点是提供Provider组件,通过这个组件把创建的仓库注入到应用当中,然后就可以在组件当中调用数据,也不是直接就可以调,而是通过connect方法,它类似withRouter,通过connect把组件进行包装。之后组件内部就直接可以调用getState方法及dispatch方法了,因为默认情况组件的props下是没有dispatch方法了,所有通过connect方法向组件props中注入dispatch方法,然后用this.props.dispatch可直接调用就行了。类似非路由组件通过withRouter进行包装,它底下就会location、history等这些对象了。

重点和难点在于异步 action,这里涉及到一个Middlewareredux的中间件。主要解决异步数据更新的问题。



(后续待补充)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值